New upstream version 6.7.3
authorStuart Prescott <stuart@debian.org>
Thu, 9 Jan 2025 04:42:26 +0000 (15:42 +1100)
committerStuart Prescott <stuart@debian.org>
Thu, 9 Jan 2025 04:42:26 +0000 (15:42 +1100)
150 files changed:
README.pyside6_addons.md
build_scripts/config.py
build_scripts/main.py
build_scripts/options.py
build_scripts/platforms/macos.py
build_scripts/platforms/unix.py
build_scripts/platforms/windows_desktop.py
build_scripts/qfp_tool.py
build_scripts/utils.py
build_scripts/wheel_files.py
build_scripts/wheel_override.py
coin/dependencies.yaml
coin/fetch_libclang_arm64.sh [new file with mode: 0644]
coin/instructions/common_environment.yaml
coin/instructions/execute_build_instructions.yaml
coin/instructions/execute_test_instructions.yaml
coin/module_config.yaml
create_wheels.py
doc/changelogs/changes-6.7.3 [new file with mode: 0644]
examples/graphs/3d/minimalsurfacegraph/minimalsurfacegraph.pyproject [new file with mode: 0644]
examples/samplebinding/doc/samplebinding.rst
examples/serialbus/modbus/modbusclient/mainwindow.py
examples/webview/minibrowser/doc/minibrowser.rst [new file with mode: 0644]
examples/webview/minibrowser/doc/minibrowser.webp [new file with mode: 0644]
examples/webview/minibrowser/images/left-32.png [new file with mode: 0644]
examples/webview/minibrowser/images/refresh-32.png [new file with mode: 0644]
examples/webview/minibrowser/images/right-32.png [new file with mode: 0644]
examples/webview/minibrowser/images/settings-32.png [new file with mode: 0644]
examples/webview/minibrowser/images/stop-32.png [new file with mode: 0644]
examples/webview/minibrowser/main.py [new file with mode: 0644]
examples/webview/minibrowser/main.qml [new file with mode: 0644]
examples/webview/minibrowser/minibrowser.pyproject [new file with mode: 0644]
examples/webview/minibrowser/qml.qrc [new file with mode: 0644]
examples/webview/minibrowser/rc_qml.py [new file with mode: 0644]
examples/widgetbinding/doc/widgetbinding.pyproject
requirements-coin.txt [new file with mode: 0644]
requirements-doc.txt
requirements.txt
sources/pyside-tools/deploy.py
sources/pyside-tools/deploy_lib/__init__.py
sources/pyside-tools/deploy_lib/android/android_config.py
sources/pyside-tools/deploy_lib/android/android_helper.py
sources/pyside-tools/deploy_lib/commands.py
sources/pyside-tools/deploy_lib/config.py
sources/pyside-tools/deploy_lib/default.spec
sources/pyside-tools/deploy_lib/dependency_util.py
sources/pyside-tools/deploy_lib/deploy_util.py
sources/pyside-tools/deploy_lib/nuitka_helper.py
sources/pyside-tools/metaobjectdump.py
sources/pyside-tools/project.py
sources/pyside-tools/pyside_tool.py
sources/pyside6/.cmake.conf
sources/pyside6/PySide6/QtAsyncio/__init__.py
sources/pyside6/PySide6/QtAsyncio/events.py
sources/pyside6/PySide6/QtAsyncio/futures.py
sources/pyside6/PySide6/QtAsyncio/tasks.py
sources/pyside6/PySide6/QtCore/typesystem_core_common.xml
sources/pyside6/PySide6/QtGui/CMakeLists.txt
sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml
sources/pyside6/PySide6/QtNetwork/typesystem_network.xml
sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml
sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml
sources/pyside6/PySide6/QtWebView/CMakeLists.txt [new file with mode: 0644]
sources/pyside6/PySide6/QtWebView/typesystem_webview.xml [new file with mode: 0644]
sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
sources/pyside6/PySide6/__init__.py.in
sources/pyside6/PySide6/glue/qtcore.cpp
sources/pyside6/cmake/Macros/PySideModules.cmake
sources/pyside6/cmake/PySideHelpers.cmake
sources/pyside6/doc/PySide6/QtAsyncio/coroutines.png [new file with mode: 0644]
sources/pyside6/doc/PySide6/QtAsyncio/index.rst
sources/pyside6/doc/commercial/index.rst
sources/pyside6/doc/deployment/deployment-nuitka.rst
sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst
sources/pyside6/doc/developer/index.rst
sources/pyside6/doc/developer/qtasyncio.rst [new file with mode: 0644]
sources/pyside6/doc/extras/QtWebView.rst [new file with mode: 0644]
sources/pyside6/doc/modules.rst
sources/pyside6/doc/quickstart.rst
sources/pyside6/doc/tools/pyside-qml.rst
sources/pyside6/doc/tutorials/basictutorial/qml.rst
sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst
sources/pyside6/doc/tutorials/qmlapp/Main/Main.qml [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/Main/logo.png [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/Main/qmldir [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/logo.png [deleted file]
sources/pyside6/doc/tutorials/qmlapp/main.py
sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst
sources/pyside6/doc/tutorials/qmlapp/view.qml [deleted file]
sources/pyside6/doc/tutorials/qmlintegration/Main/Main.qml [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlintegration/Main/qmldir [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlintegration/main.py
sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst
sources/pyside6/doc/tutorials/qmlintegration/view.qml [deleted file]
sources/pyside6/doc/tutorials/qmlsqlintegration/Main/Main.qml [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlsqlintegration/Main/qmldir [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml [deleted file]
sources/pyside6/doc/tutorials/qmlsqlintegration/main.py
sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst
sources/pyside6/doc/tutorials/qmlsqlintegration/sqlDialog.py
sources/pyside6/libpyside/dynamicqmetaobject.cpp
sources/pyside6/libpyside/globalreceiverv2.cpp
sources/pyside6/libpyside/pyside.cpp
sources/pyside6/libpyside/pysidesignal.cpp
sources/pyside6/plugins/designer/designercustomwidgets.cpp
sources/pyside6/tests/QtAsyncio/bug_2790.py [new file with mode: 0644]
sources/pyside6/tests/QtAsyncio/bug_2799.py [new file with mode: 0644]
sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_uncancel.py [new file with mode: 0644]
sources/pyside6/tests/QtCore/qmimedatabase_test.py
sources/pyside6/tests/QtMultimedia/audio_test.py
sources/pyside6/tests/QtWebView/CMakeLists.txt [new file with mode: 0644]
sources/pyside6/tests/QtWidgets/private_mangle_test.py
sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
sources/shiboken6/.cmake.conf
sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp
sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h
sources/shiboken6/ApiExtractor/abstractmetafunction.cpp
sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h
sources/shiboken6/ApiExtractor/anystringview_helpers.h
sources/shiboken6/ApiExtractor/tests/testtemplates.cpp
sources/shiboken6/ApiExtractor/typedatabase.cpp
sources/shiboken6/cmake/ShibokenHelpers.cmake
sources/shiboken6/doc/typesystem.rst
sources/shiboken6/doc/typesystem_arguments.rst
sources/shiboken6/doc/typesystem_manipulating_objects.rst
sources/shiboken6/doc/typesystem_modify_function.rst [deleted file]
sources/shiboken6/generator/generator.cpp
sources/shiboken6/generator/generatorcontext.cpp
sources/shiboken6/generator/generatorcontext.h
sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp
sources/shiboken6/generator/shiboken/cppgenerator.cpp
sources/shiboken6/generator/shiboken/cppgenerator.h
sources/shiboken6/generator/shiboken/shibokengenerator.cpp
sources/shiboken6/libshiboken/basewrapper.cpp
sources/shiboken6/libshiboken/sbkfeature_base.cpp
sources/shiboken6/libshiboken/sbkstring.cpp
sources/shiboken6/libshiboken/sbkstring.h
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
tools/create_changelog.py
tools/cross_compile_android/android_utilities.py
tools/cross_compile_android/main.py
tools/cross_compile_android/templates/cross_compile.tmpl.sh
tools/example_gallery/main.py
tools/install-p311.sh [new file with mode: 0755]
tools/snippets_translate/main.py
tools/uic_test.py

index e6044c4a4c86b0074481e3aef611db315c7cea67..b0dc0596bd562e993251e1e379265675f98f8e88 100644 (file)
@@ -48,6 +48,7 @@ it includes the following Qt modules:
 * QtHttpServer
 * QtLocation
 * QtAsyncio
+* QtWebView
 
 ### Documentation and Bugs
 
index 0a6eebf7801cb6ddfdc4cdf2abaefa63f9097bf7..5a2f6655af9689d815a5ec69dc23a6460acdac7f 100644 (file)
@@ -203,9 +203,9 @@ class Config(object):
                 _pyside_tools = available_pyside_tools(qt_tools_path=qt_install_path)
 
                 # replacing pyside6-android_deploy by pyside6-android-deploy for consistency
-                # Also, the tool should not exist in any other platform than Linux
+                # Also, the tool should not exist in any other platform than Linux and macOS
                 _console_scripts = []
-                if ("android_deploy" in _pyside_tools) and sys.platform.startswith("linux"):
+                if ("android_deploy" in _pyside_tools) and sys.platform in ["linux", "darwin"]:
                     _console_scripts = [(f"{PYSIDE}-android-deploy ="
                                         " PySide6.scripts.pyside_tool:android_deploy")]
                 _pyside_tools.remove("android_deploy")
index bf71f9962240baa096041737896807a91938d792..faa110b9883fd2f7fefc45b380217678289b2bcf 100644 (file)
@@ -6,6 +6,7 @@ import os
 import platform
 import re
 import sys
+import subprocess
 import sysconfig
 import time
 from packaging.version import parse as parse_version
@@ -160,6 +161,22 @@ def prepare_build():
                     qt_src_dir = maybe_qt_src_dir
 
 
+def get_soname(clang_lib_path: Path) -> str:
+    """Getting SONAME from a shared library using readelf. Works only on Linux.
+    """
+    clang_lib_path = Path(clang_lib_path)
+    try:
+        result = subprocess.run(['readelf', '-d', str(clang_lib_path)],
+                                capture_output=True, text=True, check=True)
+        for line in result.stdout.split('\n'):
+            if 'SONAME' in line:
+                soname = line.split('[')[1].split(']')[0]
+                return soname
+    except subprocess.CalledProcessError as e:
+        print(f"Failed to get SONAME: {e}")
+    return None
+
+
 class PysideInstall(_install, CommandMixin):
 
     user_options = _install.user_options + CommandMixin.mixin_user_options
@@ -508,7 +525,11 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin):
         log.info("-" * 3)
         if sys.platform == 'win32':
             log.info(f"OpenSSL dll directory: {OPTION['OPENSSL']}")
-        if sys.platform == 'darwin':
+        # for cross-compilation it is possible to use a macOS host, but
+        # pyside_macos_deployment_target is not relevant for the target.
+        # The only exception here is when we are trying to cross-compile from intel mac to m1 mac.
+        # This case is not supported yet.
+        if sys.platform == 'darwin' and not self.is_cross_compile:
             pyside_macos_deployment_target = (macos_pyside_min_deployment_target())
             log.info(f"MACOSX_DEPLOYMENT_TARGET set to: {pyside_macos_deployment_target}")
         log.info("=" * 30)
@@ -587,6 +608,15 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin):
             cmake_cmd.append(f"-DCMAKE_UNITY_BUILD_BATCH_SIZE={batch_size}")
             log.info("Using UNITY build")
 
+        if OPTION['SHIBOKEN_FORCE_PROCESS_SYSTEM_HEADERS']:
+            cmake_cmd.append("-DPYSIDE_TREAT_QT_INCLUDE_DIRS_AS_NON_SYSTEM=ON")
+            log.info("Shiboken will now process system Qt headers")
+
+        if OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS']:
+            extra_include_paths = ';'.join(OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS'].split(','))
+            cmake_cmd.append(f"-DSHIBOKEN_FORCE_PROCESS_SYSTEM_INCLUDE_PATHS={extra_include_paths}")
+            log.info(f"Shiboken will now process system headers from: {extra_include_paths}")
+
         cmake_cmd += [
             "-G", self.make_generator,
             f"-DBUILD_TESTS={self.build_tests}",
@@ -745,7 +775,9 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin):
 
         cmake_cmd += platform_cmake_options()
 
-        if sys.platform == 'darwin':
+        # for a macOS host, cross-compilation is possible, but for the host system as such
+        # we only build shiboken. Hence the following code can be skipped.
+        if sys.platform == 'darwin' and not self.is_cross_compile:
             if OPTION["MACOS_ARCH"]:
                 # also tell cmake which architecture to use
                 cmake_cmd.append(f"-DCMAKE_OSX_ARCHITECTURES:STRING={OPTION['MACOS_ARCH']}")
@@ -1040,6 +1072,18 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin):
                      f"folder as {basename}.")
             destination_path = destination_dir / basename
 
+            # It is possible that the resolved libclang has a different SONAME
+            # For example the actual libclang might be named libclang.so.14.0.0 and its
+            # SONAME might be libclang.so.13
+            # In this case, the ideal approach is to find the SONAME and create a symlink to the
+            # actual libclang in the destination directory. But, Python packaging (setuptools)
+            # does not support symlinks.
+            # So, we rename the actual libclang to the SONAME and copy it to the destination
+            if sys.platform == 'linux':
+                soname = get_soname(clang_lib_path)
+                if soname and soname != clang_lib_path.name:
+                    destination_path = destination_path.parent / soname
+
             # Need to modify permissions in case file is not writable
             # (a reinstall would cause a permission denied error).
             copyfile(clang_lib_path,
index bfaf03262069622bac018c029ba9a554cb7864f3..5ec36d53c3887b43bf5c3711ba55dccfba41da75 100644 (file)
@@ -247,7 +247,27 @@ class CommandMixin(object):
         ('plat-name=', None, 'The platform name for which we are cross-compiling'),
         ('unity', None, 'Use CMake UNITY_BUILD_MODE (obsolete)'),
         ('no-unity', None, 'Disable CMake UNITY_BUILD_MODE'),
-        ('unity-build-batch-size=', None, 'Value of CMAKE_UNITY_BUILD_BATCH_SIZE')
+        ('unity-build-batch-size=', None, 'Value of CMAKE_UNITY_BUILD_BATCH_SIZE'),
+        # shiboken-force-process-system-headers option is specifically used to tell the clang
+        # inside shiboken to process the system headers, when building against a system Qt.
+        #
+        # This option is specific for Flatpak and OS distro builds of PySide6. So, use with
+        # caution as it may also try to parse other global headers.
+        ('shiboken-force-process-system-headers', None,
+         'When building PySide against system Qt, shiboken does not ignore the system Qt headers'),
+        # shiboken-extra-inlude-paths option is specifically used to tell the clang inside shiboken
+        # to include extra paths when parsing the headers. Use with caution.
+        ('shiboken-extra-include-paths=', None,
+         'Extra include paths for shiboken. Comma separated.'),
+        # flatpak option is used to build PySide6 for Flatpak. Flatpak is a special case where
+        # some of the headers for the Qt modules are located as system headers in /usr/include in
+        # the KDE flatpak SDK. Therefore --shiboken-force-process-system headers will be by
+        # default enabled when --flatpak is enabled.
+        # Apart from that, headers for certain Qt modules like QtWebEngine, QtPdf etc. are located
+        # in /app/include from the Flapak WebEngine baseapp. Therefore when the --flatpak option is
+        # enabled, the extra include path of /app/include will be added to the option
+        # --shiboken-extra-include-paths.
+        ('flatpak', None, 'Build PySide6 for Flatpak.'),
     ]
 
     def __init__(self):
@@ -309,6 +329,9 @@ class CommandMixin(object):
         self.unity = False
         self.no_unity = False
         self.unity_build_batch_size = "16"
+        self.shiboken_force_process_system_headers = False
+        self.shiboken_extra_include_paths = None
+        self.flatpak = False
 
         # When initializing a command other than the main one (so the
         # first one), we need to copy the user options from the main
@@ -429,6 +452,12 @@ class CommandMixin(object):
                         "Unity build mode is now the default.")
         OPTION['UNITY'] = not self.no_unity
         OPTION['UNITY_BUILD_BATCH_SIZE'] = self.unity_build_batch_size
+        OPTION['SHIBOKEN_FORCE_PROCESS_SYSTEM_HEADERS'] = self.shiboken_force_process_system_headers
+        OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS'] = self.shiboken_extra_include_paths
+        OPTION['FLATPAK'] = self.flatpak
+        if OPTION['FLATPAK']:
+            OPTION['SHIBOKEN_FORCE_PROCESS_SYSTEM_HEADERS'] = True
+            OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS'] = '/app/include'
 
         qtpaths_abs_path = None
         if self.qtpaths and Path(self.qtpaths).exists():
index dbe60d3433bd578ce63f99f0cbf9ed859354caf2..505573e0bfcee3df8fc1c6295d4daab9a6948cd3 100644 (file)
@@ -21,7 +21,7 @@ def _macos_patch_executable(name, _vars=None):
     macos_add_rpath(rpath, binary)
 
 
-def prepare_standalone_package_macos(pyside_build, _vars):
+def prepare_standalone_package_macos(pyside_build, _vars, is_android=False):
     built_modules = _vars['built_modules']
 
     constrain_modules = None
@@ -119,7 +119,11 @@ def prepare_standalone_package_macos(pyside_build, _vars):
         ignored_modules = []
         if not pyside_build.is_webengine_built(built_modules):
             ignored_modules.extend(['libQt6WebEngine*.dylib'])
+
         accepted_modules = ['libQt6*.6.dylib']
+        if is_android:
+            accepted_modules = ['libQt6*.so', '*-android-dependencies.xml']
+
         if constrain_modules:
             accepted_modules = [f"libQt6{module}*.6.dylib" for module in constrain_modules]
 
@@ -156,6 +160,8 @@ def prepare_standalone_package_macos(pyside_build, _vars):
         # <qt>/plugins/* -> <setup>/{st_package_name}/Qt/plugins
         plugins_target = destination_qt_dir / "plugins"
         filters = ["*.dylib"]
+        if is_android:
+            filters = ["*.so"]
         copydir("{qt_plugins_dir}", plugins_target,
                 _filter=filters,
                 recursive=True,
index 3333f5f96462ca9d55102355ce89a1c4cd28487b..642b2c8744e426b36f04587c4d2a25644b980e7f 100644 (file)
@@ -124,7 +124,7 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False):
 
             script_dirs = ["qtpy2cpp_lib", "deploy_lib", "project"]
 
-            if sys.platform.startswith("linux"):
+            if sys.platform in ["linux", "darwin"]:
                 scripts.append("android_deploy.py")
                 scripts.append("requirements-android.txt")
                 script_dirs.extend(["deploy_lib/android",
@@ -237,7 +237,7 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False):
         if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build():
             _vars['built_modules'] = generated_config['built_modules']
             if sys.platform == 'darwin':
-                prepare_standalone_package_macos(pyside_build, _vars)
+                prepare_standalone_package_macos(pyside_build, _vars, is_android=is_android)
             else:
                 prepare_standalone_package_linux(pyside_build, _vars, cross_build,
                                                  is_android=is_android)
index 9c29953be2acdbe6c4c4b4f62db2df4023f034fb..32073968bed36e788b973bbc4a94eba6b28d8d3f 100644 (file)
@@ -253,9 +253,7 @@ def copy_qt_dependency_dlls(_vars, destination_qt_dir, artifacts):
 
     with tempfile.TemporaryDirectory() as temp_path:
         redist_url = "https://download.qt.io/development_releases/prebuilt/vcredist/"
-        zip_file = "pyside_qt_deps_64_2019.7z"
-        if "{target_arch}".format(**_vars) == "32":
-            zip_file = "pyside_qt_deps_32_2019.7z"
+        zip_file = "pyside_qt_deps_673_64_2019.7z"
         try:
             download_and_extract_7z(redist_url + zip_file, temp_path)
         except Exception as e:
@@ -288,11 +286,11 @@ def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars):
 
     # <qt>/bin/*.dll and Qt *.exe -> <setup>/{st_package_name}
     qt_artifacts_permanent = [
-        "avcodec-60.dll",
-        "avformat-60.dll",
-        "avutil-58.dll",
-        "swresample-4.dll",
-        "swscale-7.dll",
+        "avcodec-*.dll",
+        "avformat-*.dll",
+        "avutil-*.dll",
+        "swresample-*.dll",
+        "swscale-*.dll",
         "opengl*.dll",
         "designer.exe",
         "linguist.exe",
index abaf48fc8ee84d23cf83867b080e5605aad9778b..24221f8ea0eb9ae689c5175594abf2bbc19599ed 100644 (file)
@@ -172,7 +172,7 @@ def edit_config_file():
 """
 Config file handling, cache and read function
 """
-config_dict = {}
+config_dict: dict = {}
 
 
 def read_config_file(file_name):
@@ -263,15 +263,15 @@ def read_config_python_binary() -> str:
 
 def get_config_file(base_name) -> Path:
     global user
-    home = os.getenv('HOME')
+    home = os.getenv('HOME', default="")
     if IS_WINDOWS:
         # Set a HOME variable on Windows such that scp. etc.
         # feel at home (locating .ssh).
         if not home:
-            home = os.getenv('HOMEDRIVE') + os.getenv('HOMEPATH')
+            home = os.getenv('HOMEDRIVE', default="") + os.getenv('HOMEPATH', default="")
             os.environ['HOME'] = home
         user = os.getenv('USERNAME')
-        config_file = Path(os.getenv('APPDATA')) / base_name
+        config_file = Path(os.getenv('APPDATA', default="")) / base_name
     else:
         user = os.getenv('USER')
         config_dir = Path(home) / '.config'
@@ -290,7 +290,7 @@ def build(target: str):
     acceleration = read_acceleration_config()
     if not IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD:
         arguments.append(INCREDIBUILD_CONSOLE)
-        arguments.appendh('--avoid')  # caching, v0.96.74
+        arguments.append('--avoid')  # caching, v0.96.74
     arguments.extend([read_config_python_binary(), 'setup.py', target])
     build_arguments = read_config_build_arguments()
     if opt_verbose and LOG_LEVEL_OPTION in build_arguments:
index 74d9e6fc5ac19718b13a9f9965e27c59e38725ef..311c67f71d202793a1526d0229ef0aa83f3a9189 100644 (file)
@@ -71,7 +71,12 @@ def get_numpy_location():
         if 'site-' in p:
             numpy = Path(p).resolve() / 'numpy'
             if numpy.is_dir():
-                return os.fspath(numpy / 'core' / 'include')
+                candidate = numpy / '_core' / 'include'  # Version 2
+                if not candidate.is_dir():
+                    candidate = numpy / 'core' / 'include'  # Version 1
+                if candidate.is_dir():
+                    return os.fspath(candidate)
+                log.warning(f"Cannot find numpy include dir under {numpy}")
     return None
 
 
index 2112bba9af35c2be5ef2ac5b7d91dc3762975a41..b49906a4b7370ebb1edc2ccca48e7f8ae1e0dd02 100644 (file)
@@ -224,6 +224,7 @@ def wheel_files_pyside_addons() -> List[ModuleData]:
         module_QtHttpServer(),
         module_QtLocation(),
         module_QtAsyncio(),
+        module_QtWebView(),
     ]
     return files
 
@@ -564,9 +565,6 @@ def module_QtQuick() -> ModuleData:
         "libQt6QuickTimelineBlendTrees",
     ]
 
-    # Adding GraphicalEffects files
-    data.qml.append("Qt5Compat/GraphicalEffects")
-
     data.qtlib.extend(_qtlib)
     data.metatypes.extend(_metatypes)
     json_data = get_module_json_data("Quick")
@@ -777,6 +775,9 @@ def module_QtQuick3D() -> ModuleData:
     data.extra_files.append("qsb*")
     data.extra_files.append("balsam*")
 
+    # Adding GraphicalEffects files
+    data.qml.append("Qt5Compat/GraphicalEffects")
+
     return data
 
 
@@ -850,9 +851,17 @@ def module_QtMultimedia() -> ModuleData:
     data.translations.append("qtmultimedia_*")
     data.plugins = get_module_plugins(json_data)
 
-    if sys.platform == "win32":
-        data.extra_files.extend(["avcodec-60.dll", "avformat-60.dll", "avutil-58.dll",
-                                 "swresample-4.dll", "swscale-7.dll"])
+    platform_files = {
+        "win32": ["avcodec-*.dll", "avformat-*.dll", "avutil-*.dll", "swresample-*.dll",
+                  "swscale-*.dll"],
+        "darwin": [f"Qt/lib/{dependency_lib}" for dependency_lib in ["libavcodec.*.dylib",
+                                                                     "libavformat.*.dylib",
+                                                                     "libavutil.*.dylib",
+                                                                     "libswresample.*.dylib",
+                                                                     "libswscale.*.dylib"]]}
+
+    extra_files = platform_files.get(sys.platform, [])
+    data.extra_files.extend(extra_files)
 
     return data
 
@@ -1036,3 +1045,8 @@ def module_QtAsyncio() -> ModuleData:
 def module_QtExampleIcons() -> ModuleData:
     data = ModuleData("ExampleIcons")
     return data
+
+
+def module_QtWebView() -> ModuleData:
+    data = ModuleData("WebView")
+    return data
index f3f9f17a926f5c79a21cea4f37b198b97b51afcd..790282cd488f6f36a3818121819c7f4d6fc50ae4 100644 (file)
@@ -47,7 +47,7 @@ class PysideBuildWheel(_bdist_wheel, CommandMixin):
 
     def finalize_options(self):
         CommandMixin.mixin_finalize_options(self)
-        if sys.platform == 'darwin':
+        if sys.platform == 'darwin' and not self.is_cross_compile:
             # Override the platform name to contain the correct
             # minimum deployment target.
             # This is used in the final wheel name.
index c03b1b8ae002a0448874f1d27a83cfe0538fb460..d1a2776b0276ea8833c9c3aea8e9fcf48955a3a2 100644 (file)
@@ -1,6 +1,6 @@
 product_dependency:
   ../../qt/qt5:
-    ref: "3f005f1e2e88485dbf541200ba3fafcad6ea84ad"
+    ref: "90e86aee3dd3fdc502e65d5bf2916871ae9168fe"
 dependency_source: supermodule
 dependencies: [
       "../../qt/qt3d",
@@ -36,5 +36,6 @@ dependencies: [
       "../../qt/qtwayland",
       "../../qt/qtwebchannel",
       "../../qt/qtwebengine",
-      "../../qt/qtwebsockets"
+      "../../qt/qtwebsockets",
+      "../../qt/qtwebview",
       ]
diff --git a/coin/fetch_libclang_arm64.sh b/coin/fetch_libclang_arm64.sh
new file mode 100644 (file)
index 0000000..c99e335
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/bash
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+# Download the file
+wget -q https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z
+if [ $? -ne 0 ]; then
+    echo "Error: Failed to download libclang archive" >&2
+    exit 1
+fi
+
+# Unzip the contents to /home/qt
+7z x libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z -o/home/qt
+if [ $? -ne 0 ]; then
+    echo "Error: Failed to extract libclang archive" >&2
+    exit 1
+fi
+
+# Remove the 7z file after extraction
+rm libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z
+if [ $? -ne 0 ]; then
+    echo "Error: Failed to remove libclang archive" >&2
+    exit 1
+fi
index 441a018032116622444bb42eb65302c1db67b8a4..e9b168ec7950c10afab66b2fe3e4973d13f83f66 100644 (file)
@@ -140,8 +140,8 @@ instructions:
       variableValue: "/Users/qt/.local/bin/:"
       enable_if:
          condition: property
-         property: host.osVersion
-         equals_value: MacOS_11_00
+         property: host.os
+         equals_value: MacOS
     - type: PrependToEnvironmentVariable
       variableName: PATH
       variableValue: "/Users/qt/work/install/bin:"
@@ -204,7 +204,7 @@ instructions:
             property: host.os
             equals_value: MacOS
     - type: ExecuteCommand
-      command: "sudo apt-get install python3-pip libclang-11-dev clang -y"
+      command: "sudo apt-get install python3-pip -y"
       maxTimeInSeconds: 14400
       maxTimeBetweenOutput: 1200
       enable_if:
@@ -219,7 +219,7 @@ instructions:
       userMessageOnFailure: >
           Failed to install dependencies
     - type: ExecuteCommand
-      command: "python3 -m pip install -U setuptools==69.1.1"
+      command: "chmod +x coin/fetch_libclang_arm64.sh"
       maxTimeInSeconds: 14400
       maxTimeBetweenOutput: 1200
       enable_if:
@@ -232,10 +232,25 @@ instructions:
               property: host.arch
               equals_value: AARCH64
       userMessageOnFailure: >
-          Failed to install setuptools
+          Failed to make coin/fetch_libclang_arm64.sh executable
+    - type: ExecuteCommand
+      command: "coin/fetch_libclang_arm64.sh"
+      maxTimeInSeconds: 14400
+      maxTimeBetweenOutput: 1200
+      enable_if:
+          condition: and
+          conditions:
+            - condition: property
+              property: host.os
+              equals_value: Linux
+            - condition: property
+              property: host.arch
+              equals_value: AARCH64
+      userMessageOnFailure: >
+          Failed to download libclang from Qt servers
     - type: EnvironmentVariable
       variableName: LLVM_INSTALL_DIR
-      variableValue: "/usr/lib/llvm-11/lib"
+      variableValue: "/home/qt/libclang"
       enable_if:
         condition: and
         conditions:
@@ -245,6 +260,21 @@ instructions:
           - condition: property
             property: host.os
             equals_value: Linux
+    - type: ExecuteCommand
+      command: "python3 -m pip install -r requirements-coin.txt"
+      maxTimeInSeconds: 14400
+      maxTimeBetweenOutput: 1200
+      enable_if:
+          condition: and
+          conditions:
+            - condition: property
+              property: host.os
+              equals_value: Linux
+            - condition: property
+              property: host.arch
+              equals_value: AARCH64
+      userMessageOnFailure: >
+          Failed to install requirements-coin.txt on Linux (aarch64)
     - type: EnvironmentVariable
       variableName: interpreter
       variableValue: "python3.11"
index 317adae2a1eb7879d2c4a5dfd5e01a2ef65079ec..ec27debdabe203363d0b604632607fb001d99abe 100644 (file)
@@ -1,7 +1,7 @@
 type: Group
 instructions:
   - type: ExecuteCommand
-    command: "python3 -m pip install -U setuptools==69.1.1"
+    command: "python3 -m pip install -r requirements-coin.txt"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     enable_if:
@@ -9,7 +9,7 @@ instructions:
         property: host.os
         equals_value: MacOS
     userMessageOnFailure: >
-         Failed to install setuptools on macOS
+         Failed to install requirements-coin.txt on macOS
   - type: ExecuteCommand
     command: "python3 -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/Users/qt/work/install  --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=ALL"
     maxTimeInSeconds: 14400
@@ -48,7 +48,7 @@ instructions:
         property: host.os
         equals_value: Windows
   - type: ExecuteCommand
-    command: "{{.Env.interpreter}} -m pip install -U pip setuptools==69.1.1 --user"
+    command: "{{.Env.interpreter}} -m pip install -r requirements-coin.txt --user"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     enable_if:
@@ -56,7 +56,7 @@ instructions:
         property: host.os
         equals_value: Linux
     userMessageOnFailure: >
-         Failed to install setuptools on Linux
+         Failed to install requirements-coin.txt on Linux
   - type: ExecuteCommand
     command: "{{.Env.interpreter}} -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/home/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch={{.Env.HOST_ARCH_COIN}} --targetArch={{.Env.TARGET_ARCH_COIN}}--phase=ALL"
     maxTimeInSeconds: 14400
@@ -68,7 +68,7 @@ instructions:
     userMessageOnFailure: >
          Failed to execute build instructions on Linux
   - type: ExecuteCommand
-    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U setuptools==69.1.1"
+    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -r requirements-coin.txt"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     enable_if:
@@ -76,7 +76,7 @@ instructions:
         property: host.os
         equals_value: Windows
     userMessageOnFailure: >
-         Failed to install setuptools on Windows
+         Failed to install requirements-coin.txt on Windows
   - type: ExecuteCommand
     command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=BUILD"
     maxTimeInSeconds: 14400
index 780b27ec7ddb821205bafc5baf4d2e95548d3dff..dd9ad9b9b254add8fca0692777e502f29143c28d 100644 (file)
@@ -5,7 +5,7 @@ enable_if:
   not_contains_value: LicenseCheck
 instructions:
   - type: ExecuteCommand
-    command: "python3 -m pip install -U setuptools==69.1.1"
+    command: "python3 -m pip install -r requirements-coin.txt"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     enable_if:
@@ -13,7 +13,7 @@ instructions:
         property: host.os
         equals_value: MacOS
     userMessageOnFailure: >
-         Failed to install setuptools on macOS
+         Failed to install requirements-coin.txt on macOS
   - type: ExecuteCommand
     command: "python3 -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=ARM64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}"
     maxTimeInSeconds: 14400
@@ -22,8 +22,8 @@ instructions:
         condition: and
         conditions:
           - condition: property
-            property: host.osVersion
-            equals_value: MacOS_11_00
+            property: host.os
+            equals_value: MacOS
           - condition: property
             property: host.arch
             equals_value: ARM64
@@ -45,7 +45,7 @@ instructions:
     userMessageOnFailure: >
          Failed to execute test instructions on macOS
   - type: ExecuteCommand
-    command: "{{.Env.interpreter}} -m pip install -U pip setuptools==69.1.1 --user"
+    command: "{{.Env.interpreter}} -m pip install -r requirements-coin.txt --user"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     enable_if:
@@ -53,7 +53,7 @@ instructions:
         property: host.os
         equals_value: Linux
     userMessageOnFailure: >
-         Failed to install setuptools on Linux
+         Failed to install requirements-coin.txt on Linux
   - type: ExecuteCommand
     command: "{{.Env.interpreter}} -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/home/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}"
     maxTimeInSeconds: 14400
@@ -65,7 +65,7 @@ instructions:
     userMessageOnFailure: >
          Failed to execute test instructions on Linux
   - type: ExecuteCommand
-    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U pip setuptools==69.1.1 --user"
+    command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -r requirements-coin.txt --user"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     enable_if:
@@ -73,7 +73,7 @@ instructions:
         property: host.os
         equals_value: Windows
     userMessageOnFailure: >
-         Failed to install setuptools on Windows
+         Failed to install requirements-coin.txt on Windows
   - type: ExecuteCommand
     command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=c:\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}"
     maxTimeInSeconds: 14400
index e5c2fdc8c578ee12f3bcf2662a575e2692e56691..52e89a033fa824928d50f5f67f907ea1a0e6f01d 100644 (file)
@@ -51,20 +51,9 @@ accept_configuration:
         - condition: property
           property: target.os
           not_contains_value: IOS
-    - condition: and
-      conditions:
         - condition: property
           property: host.osVersion
-          equals_value: MacOS_11_00
-        - condition: property
-          property: host.arch
-          equals_value: ARM64
-        - condition: property
-          property: features
-          contains_value: TestOnly
-        - condition: property
-          property: features
-          contains_value: Packaging
+          not_equals_value: MacOS_12
     - condition: and # Restore LoA config
       conditions:
         - condition: property
index 063b59c450eb079dbe470fcf6bdc1b47cc01133e..3ccef280b64c6d724af5aa477e1cda8ebd7af8bb 100644 (file)
@@ -29,13 +29,13 @@ PYSIDE_DESCRIPTION = "Python bindings for the Qt cross-platform application and
 @dataclass
 class SetupData:
     name: str
-    version: str
+    version: tuple[str, str]
     description: str
     readme: str
     console_scripts: List[str]
 
 
-def get_version_from_package(name: str, package_path: Path) -> str:
+def get_version_from_package(name: str, package_path: Path) -> tuple[str, str]:
     # Get version from the already configured '__init__.py' file
     version = ""
     with open(package_path / name / "__init__.py") as f:
@@ -122,7 +122,11 @@ def get_platform_tag() -> str:
 
         module_name = config_py.name[:-3]
         _spec = importlib.util.spec_from_file_location(f"{module_name}", config_py)
+        if _spec is None:
+            raise RuntimeError(f"Unable to create ModuleSpec from {str(config_py)}")
         _module = importlib.util.module_from_spec(_spec)
+        if _spec.loader is None:
+            raise RuntimeError(f"ModuleSpec for {module_name} has no valid loader.")
         _spec.loader.exec_module(module=_module)
         target = _module.__qt_macos_min_deployment_target__
 
@@ -254,9 +258,9 @@ def wheel_pyside6_essentials(package_path: Path) -> Tuple[SetupData, List[Module
     _pyside_tools = available_pyside_tools(packaged_qt_tools_path, package_for_wheels=True)
 
     # replacing pyside6-android_deploy by pyside6-android-deploy for consistency
-    # Also, the tool should not exist in any other platform than Linux
+    # Also, the tool should not exist in any other platform than Linux and macOS
     _console_scripts = []
-    if ("android_deploy" in _pyside_tools) and sys.platform.startswith("linux"):
+    if ("android_deploy" in _pyside_tools) and sys.platform in ("linux", "darwin"):
         _console_scripts = ['pyside6-android-deploy = "PySide6.scripts.pyside_tool:android_deploy"']
     _pyside_tools.remove("android_deploy")
 
diff --git a/doc/changelogs/changes-6.7.3 b/doc/changelogs/changes-6.7.3
new file mode 100644 (file)
index 0000000..93ea360
--- /dev/null
@@ -0,0 +1,75 @@
+Qt for Python 6.7.3 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ - A --flatpak option has been added to setup.py, enabling a flatpak build
+   of Qt for Python.
+ - [PYSIDE-769]  QtAsyncio: The application argument has been removed
+                 from the loop policy.
+ - [PYSIDE-1612] Deployment: Nuitka has been updated to 2.3.7. Scanning for
+                 QML dependencies has been fixed to skip some directories.
+ - [PYSIDE-1612] Android Cross Compilation: INSTSONAME has been added.
+ - [PYSIDE-1877] Properties of type QAbstractItemModel can now be used in QML.
+ - [PYSIDE-2192] PySide Qt Gui applications can now be used in interactive
+                 mode, for example notebooks.
+ - [PYSIDE-2517] Type hints: The signatures of QObject.findChild()/
+                 findChildren() have been improved to reflect the type passed
+                 in.
+ - [PYSIDE-2622] Deployment: Nuitka --standalone mode is now supported.
+ - [PYSIDE-2656] QtMultimedia on macOS has been fixed.
+ - [PYSIDE-2702] An option to force processing system headers has been added
+                 for cases where Qt is installed into the system.
+ - [PYSIDE-2752] Type hints: A syntax error caused by empty Enums has been
+                 fixed.
+ - [PYSIDE-2766] Android Deployment: pyside6-android-deploy now works macOS,
+                 too.
+ - [PYSIDE-2785] Deployment: 'dist-packages' is now skipped similar
+                 to 'site-packages' when scanning for QML dependencies.
+ - [PYSIDE-2788] Type hints: The signature of QFormLayout.getLayoutPosition()
+                 has been fixed.
+ - [PYSIDE-2789] numpy 2.0 is now supported.
+ - [PYSIDE-2790] QtAsyncio: cancel count and uncancel() have been added.
+ - [PYSIDE-2796] A potential crash in currentOpcode_Is_CallMethNoArgs()
+                 has been fixed.
+ - [PYSIDE-2799] QtAsyncio: A hang when an exception occurs inside a
+                 TaskGroup body has been fixed.
+ - [PYSIDE-2803] Desktop Deployment: Overflows of command lines on Windows
+                 have been fixed.
+ - [PYSIDE-2806] Desktop Deployment: The application name has been fixed.
+ - [PYSIDE-2814] Deployment: Arguments with spaces can now be used for
+                 "extra_args" due to using shlex for splitting the command
+                 line arguments.
+ - [PYSIDE-2819] The correct libclang is now used for arm64.
+ - [PYSIDE-2825] QtWebView has been added.
+ - [PYSIDE-2828] Documentation: The .qrc tutorial has been updated.
+ - [PYSIDE-2833] The QML tutorials have been updated.
+ - [PYSIDE-2834] QDir.entry(Info)List(QDir.Filter, QDir.SortFlags)
+                 has been fixed to work with Python 3.11 and later.
+ - [PYSIDE-2836] PySide6/__init__.py now has a static list of modules,
+                 enabling code checkers to work.
+ - [PYSIDE-2870] A crash when using QStateMachine.postEvent() has been
+                 fixed.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+- [PYSIDE-2834] Enumerations have been excluded from argument type checks
+                for sequences. This addresses a problem showing in Python
+                3.11 causing the wrong function overloads to be used.
+- [PYSIDE-2780] A potential refcounting bug in Lazy loading has been fixed.
diff --git a/examples/graphs/3d/minimalsurfacegraph/minimalsurfacegraph.pyproject b/examples/graphs/3d/minimalsurfacegraph/minimalsurfacegraph.pyproject
new file mode 100644 (file)
index 0000000..cc7a74a
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py"]
+}
index 51b6b4c20297397fd4f3a0363dd3214381866819..f28798d5211cb4715be9fab0ba92c171f1ba8c46 100644 (file)
@@ -186,6 +186,7 @@ Run CMake on Windows:
 To build:
 
 .. code-block:: bash
+
     ninja
     ninja install
     cd ..
index 02f9d478b9fba6aa85def24c85d328d8f4fd7d88..071b3d4394cabb9736e92fb841c57318d8e329e7 100644 (file)
@@ -1,14 +1,12 @@
 # Copyright (C) 2022 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
-import functools
 from enum import IntEnum
 
 from PySide6.QtCore import QUrl, Slot
 from PySide6.QtGui import QStandardItemModel, QStandardItem
 from PySide6.QtWidgets import QMainWindow
-from PySide6.QtSerialBus import (QModbusDataUnit,
-                                 QModbusDevice, QModbusReply,
+from PySide6.QtSerialBus import (QModbusDataUnit, QModbusDevice,
                                  QModbusRtuSerialClient, QModbusTcpClient)
 
 from ui_mainwindow import Ui_MainWindow
@@ -111,7 +109,7 @@ class MainWindow(QMainWindow):
         elif index == ModbusConnection.TCP:
             self._modbus_device = QModbusTcpClient(self)
             if not self.ui.portEdit.text():
-                self.ui.portEdit.setText("127.0.0.1:502")
+                self.ui.portEdit.setText("127.0.0.1:50200")
 
         self._modbus_device.errorOccurred.connect(self._show_device_errorstring)
 
@@ -188,7 +186,7 @@ class MainWindow(QMainWindow):
                                                     self.ui.serverEdit.value())
         if reply:
             if not reply.isFinished():
-                reply.finished.connect(functools.partial(self.onReadReady, reply))
+                reply.finished.connect(self.onReadReady)
             else:
                 del reply  # broadcast replies return immediately
         else:
@@ -196,7 +194,8 @@ class MainWindow(QMainWindow):
             self.statusBar().showMessage(message, 5000)
 
     @Slot()
-    def onReadReady(self, reply):
+    def onReadReady(self):
+        reply = self.sender()
         if not reply:
             return
 
@@ -248,13 +247,16 @@ class MainWindow(QMainWindow):
                 # broadcast replies return immediately
                 reply.deleteLater()
             else:
-                reply.finished.connect(functools.partial(self._write_finished, reply))
+                reply.finished.connect(self._write_finished)
         else:
             message = "Write error: " + self._modbus_device.errorString()
             self.statusBar().showMessage(message, 5000)
 
-    @Slot(QModbusReply)
-    def _write_finished(self, reply):
+    @Slot()
+    def _write_finished(self):
+        reply = self.sender()
+        if not reply:
+            return
         error = reply.error()
         if error == QModbusDevice.ProtocolError:
             e = reply.errorString()
@@ -289,7 +291,7 @@ class MainWindow(QMainWindow):
                                                          self.ui.serverEdit.value())
         if reply:
             if not reply.isFinished():
-                reply.finished.connect(functools.partial(self.onReadReady, reply))
+                reply.finished.connect(self.onReadReady)
             else:
                 del reply  # broadcast replies return immediately
         else:
diff --git a/examples/webview/minibrowser/doc/minibrowser.rst b/examples/webview/minibrowser/doc/minibrowser.rst
new file mode 100644 (file)
index 0000000..334b95b
--- /dev/null
@@ -0,0 +1,8 @@
+Minibrowser Example
+===================
+
+Simple application that demonstrates how to use a QWebView modules with Qt Quick.
+
+.. image:: minibrowser.webp
+   :width: 800
+   :alt: Minibrowser screenshot
diff --git a/examples/webview/minibrowser/doc/minibrowser.webp b/examples/webview/minibrowser/doc/minibrowser.webp
new file mode 100644 (file)
index 0000000..b7aca89
Binary files /dev/null and b/examples/webview/minibrowser/doc/minibrowser.webp differ
diff --git a/examples/webview/minibrowser/images/left-32.png b/examples/webview/minibrowser/images/left-32.png
new file mode 100644 (file)
index 0000000..28e4dda
Binary files /dev/null and b/examples/webview/minibrowser/images/left-32.png differ
diff --git a/examples/webview/minibrowser/images/refresh-32.png b/examples/webview/minibrowser/images/refresh-32.png
new file mode 100644 (file)
index 0000000..886cad4
Binary files /dev/null and b/examples/webview/minibrowser/images/refresh-32.png differ
diff --git a/examples/webview/minibrowser/images/right-32.png b/examples/webview/minibrowser/images/right-32.png
new file mode 100644 (file)
index 0000000..00ccf43
Binary files /dev/null and b/examples/webview/minibrowser/images/right-32.png differ
diff --git a/examples/webview/minibrowser/images/settings-32.png b/examples/webview/minibrowser/images/settings-32.png
new file mode 100644 (file)
index 0000000..948d90e
Binary files /dev/null and b/examples/webview/minibrowser/images/settings-32.png differ
diff --git a/examples/webview/minibrowser/images/stop-32.png b/examples/webview/minibrowser/images/stop-32.png
new file mode 100644 (file)
index 0000000..3f5fb8b
Binary files /dev/null and b/examples/webview/minibrowser/images/stop-32.png differ
diff --git a/examples/webview/minibrowser/main.py b/examples/webview/minibrowser/main.py
new file mode 100644 (file)
index 0000000..bee3189
--- /dev/null
@@ -0,0 +1,60 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QCoreApplication, QUrl, QRect, QPoint
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+from PySide6.QtWebView import QtWebView
+import argparse
+
+import rc_qml  # noqa: F401
+
+
+class Utils:
+    @staticmethod
+    def fromUserInput(userInput):
+        if not userInput:
+            return QUrl.fromUserInput("about:blank")
+        result = QUrl.fromUserInput(userInput)
+        return result if result.isValid() else QUrl.fromUserInput("about:blank")
+
+
+if __name__ == "__main__":
+    QtWebView.initialize()
+    app = QGuiApplication(sys.argv)
+    QGuiApplication.setApplicationDisplayName(QCoreApplication
+                                              .translate("main", "QtWebView Example"))
+
+    parser = argparse.ArgumentParser(description=QGuiApplication.applicationDisplayName())
+    parser.add_argument("--url", nargs="?",
+                        default="https://www.qt.io",
+                        help="The initial URL to open.")
+    args = parser.parse_args()
+    initialUrl = args.url
+
+    engine = QQmlApplicationEngine()
+    context = engine.rootContext()
+    context.setContextProperty("utils", Utils())
+    context.setContextProperty("initialUrl", Utils.fromUserInput(initialUrl))
+
+    geometry = QGuiApplication.primaryScreen().availableGeometry()
+    if not QGuiApplication.styleHints().showIsFullScreen():
+        size = geometry.size() * 4 / 5
+        offset = (geometry.size() - size) / 2
+        pos = geometry.topLeft() + QPoint(offset.width(), offset.height())
+        geometry = QRect(pos, size)
+
+    engine.setInitialProperties({"x": geometry.x(), "y": geometry.y(),
+                                 "width": geometry.width(), "height": geometry.height()})
+    qml_file = Path(__file__).parent / "main.qml"
+    engine.load(QUrl.fromLocalFile(qml_file))
+
+    if not engine.rootObjects():
+        sys.exit(-1)
+
+    ex = app.exec()
+    del engine
+    sys.exit(ex)
diff --git a/examples/webview/minibrowser/main.qml b/examples/webview/minibrowser/main.qml
new file mode 100644 (file)
index 0000000..f647c62
--- /dev/null
@@ -0,0 +1,153 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtWebView
+import QtQuick.Layouts
+
+
+ApplicationWindow {
+    id: window
+    visible: true
+    title: webView.title
+
+    menuBar: ToolBar {
+        id: navigationBar
+        RowLayout {
+            anchors.fill: parent
+            spacing: 0
+
+            ToolButton {
+                id: backButton
+                icon.source: "qrc:/left-32.png"
+                onClicked: webView.goBack()
+                enabled: webView.canGoBack
+                Layout.preferredWidth: navigationBar.height
+            }
+
+            ToolButton {
+                id: forwardButton
+                icon.source: "qrc:/right-32.png"
+                onClicked: webView.goForward()
+                enabled: webView.canGoForward
+                Layout.preferredWidth: navigationBar.height
+            }
+
+            ToolButton {
+                id: reloadButton
+                icon.source: webView.loading ? "qrc:/stop-32.png" : "qrc:/refresh-32.png"
+                onClicked: webView.loading ? webView.stop() : webView.reload()
+                Layout.preferredWidth: navigationBar.height
+            }
+
+            Item { Layout.preferredWidth: 5 }
+
+            TextField {
+                Layout.fillWidth: true
+                id: urlField
+                inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhPreferLowercase
+                text: webView.url
+                onAccepted: webView.url = utils.fromUserInput(text)
+             }
+
+            Item { Layout.preferredWidth: 5 }
+
+            ToolButton {
+                id: goButton
+                text: qsTr("Go")
+                onClicked: {
+                    Qt.inputMethod.commit()
+                    Qt.inputMethod.hide()
+                    webView.url = utils.fromUserInput(urlField.text)
+                }
+            }
+
+            ToolButton {
+                id: settingsButton
+                icon.source: "qrc:/settings-32.png"
+                onClicked: {
+                    settingsDrawer.width = (settingsDrawer.width > 0) ? 0 : window.width * 1/4
+                }
+                Layout.preferredWidth: navigationBar.height
+            }
+
+            Item { Layout.preferredWidth: 10 }
+         }
+         ProgressBar {
+             id: progress
+             anchors {
+                left: parent.left
+                top: parent.bottom
+                right: parent.right
+                leftMargin: parent.leftMargin
+                rightMargin: parent.rightMargin
+             }
+             height:3
+             z: Qt.platform.os === "android" ? -1 : -2
+             background: Item {}
+             visible: Qt.platform.os !== "ios" && Qt.platform.os !== "winrt"
+             from: 0
+             to: 100
+             value: webView.loadProgress < 100 ? webView.loadProgress : 0
+        }
+    }
+
+    Item {
+        id: settingsDrawer
+        anchors.right: parent.right
+        ColumnLayout {
+            Label {
+                text: "JavaScript"
+            }
+            CheckBox {
+                id: javaScriptEnabledCheckBox
+                text: "enabled"
+                onCheckStateChanged: webView.settings.javaScriptEnabled = (checkState == Qt.Checked)
+            }
+            Label {
+                text: "Local storage"
+            }
+            CheckBox {
+                id: localStorageEnabledCheckBox
+                text: "enabled"
+                onCheckStateChanged: webView.settings.localStorageEnabled = (checkState == Qt.Checked)
+            }
+            Label {
+                text: "Allow file access"
+            }
+            CheckBox {
+                id: allowFileAccessEnabledCheckBox
+                text: "enabled"
+                onCheckStateChanged: webView.settings.allowFileAccess = (checkState == Qt.Checked)
+            }
+            Label {
+                text: "Local content can access file URLs"
+            }
+            CheckBox {
+                id: localContentCanAccessFileUrlsEnabledCheckBox
+                text: "enabled"
+                onCheckStateChanged: webView.settings.localContentCanAccessFileUrls = (checkState == Qt.Checked)
+            }
+        }
+    }
+
+    WebView {
+        id: webView
+        url: initialUrl
+        anchors.right: settingsDrawer.left
+        anchors.left: parent.left
+        height: parent.height
+        onLoadingChanged: function(loadRequest) {
+            if (loadRequest.errorString)
+                console.error(loadRequest.errorString);
+        }
+
+        Component.onCompleted: {
+            javaScriptEnabledCheckBox.checkState = settings.javaScriptEnabled ? Qt.Checked : Qt.Unchecked
+            localStorageEnabledCheckBox.checkState = settings.localStorageEnabled ? Qt.Checked : Qt.Unchecked
+            allowFileAccessEnabledCheckBox.checkState = settings.allowFileAccess ? Qt.Checked : Qt.Unchecked
+            localContentCanAccessFileUrlsEnabledCheckBox.checkState = settings.localContentCanAccessFileUrls ? Qt.Checked : Qt.Unchecked
+        }
+    }
+}
diff --git a/examples/webview/minibrowser/minibrowser.pyproject b/examples/webview/minibrowser/minibrowser.pyproject
new file mode 100644 (file)
index 0000000..4661706
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "files": ["main.py", "main.qml", "qml.qrc", "images/left-32.png", "images/right-32.png",
+              "images/refresh-32.png", "images/settings-32.png", "images/stop-32.png"]
+}
diff --git a/examples/webview/minibrowser/qml.qrc b/examples/webview/minibrowser/qml.qrc
new file mode 100644 (file)
index 0000000..f360471
--- /dev/null
@@ -0,0 +1,9 @@
+<RCC>
+    <qresource prefix="/">
+        <file>main.qml</file>
+        <file alias="left-32.png">images/left-32.png</file>
+        <file alias="stop-32.png">images/stop-32.png</file>
+        <file alias="refresh-32.png">images/refresh-32.png</file>
+        <file alias="right-32.png">images/right-32.png</file>
+    </qresource>
+</RCC>
diff --git a/examples/webview/minibrowser/rc_qml.py b/examples/webview/minibrowser/rc_qml.py
new file mode 100644 (file)
index 0000000..104eadb
--- /dev/null
@@ -0,0 +1,368 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.7.2
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x02\x7f\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x06bKGD\x00i\x00\xa1\x006za\
+\x0c\x8d\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\
+\xdf\x01\x1a\x09+7\xecd\xf9\xf8\x00\x00\x02\x0cID\
+ATX\xc3\xcd\x97\xb1K#A\x14\xc6\xbfy\xbb\x90\
+\x85\xa4\xb0\x91\x03\x8b\x88WX\xdc),\xd8X\xad6\
+f\x09X\xab\x95r\x049\xb0\xd2\x08\xfe\x11\xa2I\xe0\
+@\x10\x11\xb1\x10\xf4\x8a\x03+1\x07G\xd8\xea@\x02\
+)\xbc+,\x84\xa4\x10\xd16\xc2.$3\x16\xee\x84\
+\xf5\xdcpF7\x93L\xb9;\xcb\xef{\xdf\xbc\xf7\xf6\
+\x0d\xd0\xe3\xc5:\xd9\xbc\xbff\x99\x86\xc7\x96\x12\x0f\x94\
+NT\x1b\xa3h\xfa\xdfk\x10\xf5a\xfd\xaa\x1e\xe7g\
+nL\x1cf\xf2N%2\x01{\xeb\x16\x19\x1e\xcb\x0e\
+]\xf2\xcd\x16\xf0\x7fK\x83\xb8\x19\xa3\x0d7&r\xcb\
+\xdb\x0e\x7f\xb3\x80\x83Uk&Y\x11\xe7\xaf\x06\x87\x08\
+\xa9\x99\xcc\xfeRp~\xb6\xdbB\xed^\x1c\xadL\xe5\
+\x93eQ|3\x1c\x00\x9a`\xc9\xb2(\x1e\xadL\xe5\
+;r\xe0\xf8\xeb\xf4\xc9\xe0\xdf\xe6\x5c\x94\xc9v\xffI\
+\xfb\xbe\xb0[\x9a\x7fiRH\xe4\x1f\xfe\xf0L\xd4\xd9\
+\x1e\xbf\x17\x9f\xed\xd9\x91\x81\x1f\x17\xd5\xf3\xb6\x0e\x1c\xac\
+Z3\xc9\xb2(v\xb3\xecj\x13,\x15\xcc\x09\x16\xcc\
+\xf6\x8fe\xd1x\xd7\x99\xbf21\xaf'\x98.\xabC\
+\x97\xcf\x0d\x8fe\xd1\x14\xa1p2\x0cL\x17;3\xa6\
+\x94J\x81\xbbnhb\x1a\x1e\xcb\x02\xd8zV\x05C\
+\x97|SU\xf7\x0b\xb2Hv\xb8\xae[\xff\x8f\x0b\xfb\
+k\x96\xd9\x12`xlI\xf5?@2\x09\x00\x12\x0f\
+\x94V-@2\x9f\x04T\x1b\xa3\xca\x05\xf8L\x92g\
+\xa2\xfc?\xec3\xa9\xd7\xf3\x00\xc9\xe6\xa0\x9c\xec3\x09\
+\x00\xea\xc3\xfa\x95j\xbed>\x09\x88\xf33\xe5\x02|\
+&\x01\x80\x1b\x13\x87\xaa\x05H&\x01@&\xefT\x94\
+\xe6\x81\x06!\xe7\xc6V\x15\xdc\x8c\xd1\x86*~\x90\xa5\
+\x07,\xc9AC\xe8\xe0\xc9]\x17\xbf,+\xb2\xe8\xdd\
+\x98\xc8\xbd\x98\x09\x97\xb7\x1d^3\x99\xdd\xed\xe8k&\
+\xb3\x83\x93\xf2\xb3\x91\xec\xf4w\xf5\xda\x9e\x1d\x19H\xdc\
+\x89\xc9n\xc0o\xc7\xa9\xb0\xf8\xcd\xd9\xe9\xab\xa14\xb4\
+\x15/\xec\x96\xe6o\xc7\xa9\x10e\xe4a\xf0\xbe\xb8\x98\
+\xf4\xf7\xd5L\xc5\xe5\xb4\xe7\xeb\x11\x07R\xed#?\x12\
+G\x0e\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x05\x15\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x06bKGD\x00\xd9\x00M\x00M\x0d\x89\
+\x85\xe9\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\
+\xdf\x01\x17\x0f!*\x83j\xd9\xc4\x00\x00\x04\xa2ID\
+ATX\xc3\xcd\x97[L\x93g\x18\xc7\x7f_)\x05\
+Z\x0a\x149\xaa\x84\x83\xe0&\xa2\x11g\xc0L\xdc\x92\
+ZL\x06\xd9\xc6\x95\xcb\x8e\xbd\xf1b\xa0u\xa7\xbb\x1d\
+b\xdc\xc5\xee\xb69\xaa\xf4\xc6,a\xd9\x5cf\xb2\xc4\
+]\xa8\x09\x87&\x1b\x90(q\xe0\x1c\xe2\x06(8\x0f\
+\x9c\x84ZX\x81r\xe8\xbb\x0b\xfbu\xdf\xd7\x16\xf8\x98\
+K\xf4M\x9a4\xcf\xfb\xbc\xcf\xff\xff>\x87\xf7y>\
+x\xccKZ\x8brmcY6\xb0\x17\xa8\x04*\x80\
+\xbc\xe0\xd6\x10\xd0\x0e4\x03m.{\xe7\xf0\xffF\xa0\
+\xb6\xb1L\x02\xac@=P\xac\xd1n/p\x04p\xbb\
+\xec\x9d\xe2?\x13\xa8m,+\x02\xce\xae\x018\x1a\x91\
+\x1a\x97\xbd\xb3\x7f9\x05\xdd\x0a\xe0\x07\x80\xbeG\x00'\
+x\xb6/hK\xbb\x07j\x1b\xcb\x0e\x01'\xc2\xe5\xa9\
+\xa6,Js\xad\x14e\xee$;9\x1fS\x5c\x0a\x82\
+\x00\xd3s\x1e\xeez\x06\xb8~\xef\x22]C-\xcc-\
+\xceD3{\xd8e\xef<\xb9*\x81 \xdb\x1f\x94\xb2\
+\xac\xe4<^.\xad\xa3dc\x05:)\xd2i\xb3\xf3\
+>\xbc\xb3\xe3H\x92\x8e\xf8X\x13?\xffq\x86\x96\xde\
+\xefX\x0a,\x86\xab\xbe\xe2\xb2w\x9eY\x96@0\xe6\
+}\xffnJT\x96\xbcE\xf5\xf6\x83\xc4\xea\x0d*K\
+\xfe\x85Y\xda\xfb\xcfr\xf1\xc69\xeey\x06\x10<\xcc\
+5\x9d\x14C~\xfa66Z\x8a\xb8<\xd4\x84\xcf\xef\
+\x0d'\xb1Y\x99\x13RX\xb6\xf7\xc81\x97\x90x\xf3\
+\xd9O\xd8]X\x0d\x80\x10\x02I\x92\x10B08\xde\
+\xc3\xd7m\x1f3\xe9\x1bY1\x01L\x86d|\xf3\xde\
+h\x89Y\x22W\x87\xd2\x9fVe\xc2\xd5\xec<L\xf9\
+\xa6*\x84\x10\x08!B$\xfaF~\xe5xS\xdd\xaa\
+\xe0@4p91\xad\xd1\xaa\xa0^\xfe\xb3)c\x07\
+\xd6\xe2W\x91$I\xf5\xf3\xceNp\xea\x97\x0fY\x0c\
+\xcck.\x03\x83>!\x9a\xb8^E \xf8\xc2\x85n\
+\xffR\xe9\xdb\xc4\xe8bB7\x97o\x7f\xfe\xb7S\xd1\
+b\xba\xec*H\xdf\xce\xa75?\x92\x9f\xb6-\xc2\x0b\
+A\xcc\x90\x07\xf6\xca;\x99I\xb9\x14f\xecP\xb9]\
+\x08\xc1\xfc\xe2\x1c\x97n\x9e\xd7\x0c\x9e\x93\xfa\x14\xb5\xd6\
+\xcf1'\xa4R\xb7\xef\x0b6X\x8a\xc2U\xf6*\x09\
+T\x86R4kWT\x837\xc7\xaf\xb2\xb0\xe4\xd7\x04\
+\x9e\x99\x94\xcb!\xeb\x97\x18\x0df\x00\x8c\x063\x8e}\
+_\x91n\xceQ\xaaU*\x09T\xc8\xd2\x8c\xa4\x9c\x88\
+\xd8K\x92\xc4\xf8\xf4\x1dM\xe0\xa9\xa6,\x1c6'I\
+\xc6u\xaa\xf3I\xc6u\x1c\xa9tb1f\xa2\xc4\x94\
+\x09\xe4\xed\xdb\xf2\x1a\xf6=G)\xd9\xb0G\xe5~y\
+\x15f\x94b\xdfs\x14[\xf1\x1b\xcb\x82\x9b\xe3Sq\
+\xd8\x9cXL\x19\xaa\xea\x91m\xadK\xcc\xc6Q\xe9$\
+1\xce\x82\xdcICUp\xe9\xe6\x05r\xd3\xb6\x92\x91\
+\x94\xa3\x02\x97\x0de\xa7\xe4\x93\x97V\xb2b\x1e\x94\x17\
+T1t\xbf\x87\xe1\x07\x83*`\xd9\xd6\xa8\xf7/n\
+\xdd\xbfFy\xc1\x0b\x11\xcdh\xe8o\xbf\x07g\xb3\x83\
+I\xdfH\xd4\x10x|c8[\x1cL\xcfM.K\
+\xa0\xa5\xf7[\x1a;\x8e10\xd6\x1d\xd5F\xcf\xdd\x0e\
+\x1a;\x8e\xd1z\xfd\xb4<C\x84\x08\xb4\x03xfF\
+\xa9ov053\xa1\xf2\xc2\xd4\xcc\x04\xce\x16\x87\xa6\
+\xc7\x07 \xdd\xbc1B&\x84`l\xea\xb6R\xd4\xae\
+$\xd0,K\xc7\xa7o\xe3l}\x07\x9f\x7f\xea\xe1k\
+\xe6\x9f\xe2\xa4\xfb=F\xa7ni\x02\x8f\x8d\x89\xa3 \
+}{\xc8\xf5\xca\x10\xf4\x8d\x5cV\xaa6+\x09\xb4)\
+w\xeez\xfaih}\x9f\xe9\xd9I\x5c\xee\x0f\xb8=\
+\xf9\xa7\xe6\xfa//\xa8\xc2\xa0\x8f\x8f\x90\x0f\x8c]\x09\
+\xbfD[x3\xba\x16>|\x18\xf4\x09\xcc/\xcej\
+\x067\xc5%\xf3\xd1\x8b\xa7I1\xa6\xa9\xe4K\x81%\
+\x8e7\xd5qc\xecJ\xa8!\xb9\xec\x9d[\xc3{\xc1\
+\x91p\x83k\x01\xd7\xeb\x0c\x1c|\xee3R\x8ci\x11\
+\xaew\xf7~\xaf\x04Wa)\x09\xb8\x83\xad2\xa2\xa5\
+jy|\xde\xdd\xdf\xc0\xe6\xacgT\xb5\x0fpq\xe0\
+\x1cg\xbbN\x84\xb7c\xb7\xa6\x81Dv\xeb\xae\xbc\xfd\
+\xdc\xf1\xf438\xfe;\x01\xb1\x14\x1aV\xd6[\x0a\xd9\
+\xbd\xa9\x9a\x8a\xa2\x1a\xe2b\x13T3\xc3\xe2\xd2\x02\xe7\
+\xae\x9e\xa2\xb9\xe7\x9b\xd0\xb0\xb2\xe2@\xb2\xd2H\x16\xa3\
+\xd3c+~\x9d\xe7\x9f>\xc0\xdc\x82\x0f!\x02$'\
+\xa4c\x8cK\x8c(\xb5\x80\x08\xd0s\xa7\x9d\x9f\xba\x1b\
+\x18\xf1\x0e\xadm$[m(\x8d\xd7\x1b\xd9\x99gc\
+\xcb\xfa\xddl\xb0\x14b\x8e\xb7 \xa1\xc3\xe7\x7f\xc0\xb0\
+w\x90\xfe\xd1.\xbao\xb9\x97{/\xb4\x0d\xa5+y\
+\xe2\x11V\xc4\xcd\x9f\xfc\x0f\x13\x80\xe0\xc1\x12\xc0\x16\xad\
+BV\x01\xb6\x05\x87\xcf\xfe'\xfa\xe3\xf4\xb1\xaf\x7f\x00\
+\x9e\xe8\x03h^\xfe7\xb1\x00\x00\x00\x00IEND\
+\xaeB`\x82\
+\x00\x00\x03?\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x06bKGD\x00i\x00\xa1\x006za\
+\x0c\x8d\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\
+\xdf\x01\x17\x0f)\x18\x83d\x02L\x00\x00\x02\xccID\
+ATX\xc3\xcd\x97\xcfKTQ\x14\xc7?o&\x89\
+\x22P\xc8~)\xd9\xd0/$tF\x03g\xe5d\x98\
+#I?\xd0\x8d\xd2\x22'\xc5\xa2A\xe9\x0f\xe8\xc7\xa6\
+ h;\x10\x13\x96\xc6\xe0J7\x0dd\xb8\x18\x1c\xc4\
+\xb1\xcdc\xcaQ$r\x5c\xe4 \xa6\x1b\x17B\xe0F\
+}-z\xef\xf1\xe6\xbd\xf9\xf1\xde\xa8\xe9Y\xbdw\xee\
+\xb9\xf7\xfb\xe5\x9c\xef\xbd\xf7\x5c\xd8g\x13\xac\x04\xfbC\
+\xee3\x80\x07\xf0\x02\xf5\x80C\x1eZ\x04\xa6\x80\x08\x10\
+\x0b\xfa\xc4\x95]#\xe0\x0f\xb9\x05\xa0\x11\x08\x00WL\
+\xae\xfb\x03x\x02D\x83>Q*\x98\x80?\xe4\xbe\x04\
+\x84-\x00g\x22\xd2\x1a\xf4\x89\x0b\xd9\x02l9\xc0\xdb\
+\x81\xe4\x0e\xc0\x91\xe7&\xe5\xb5\xcc\x13\xf0\x87\xdc\xbd\xc0\
+\xf0.jmX^3\x7f\x09d\xb6\xc3{$\xfa\x8e\
+\xa0O\x1c\xc9J@\xaey2\xdf*\xd7+\xdb)=\
+Vn\xf0\xcf-\x7f\xe5\xe7\x8a\x98o\xfae\xad&\x04\
+\x9d\xda\xe7\xf2\xd5\xbc\xb5\xb6\x17o\xd5}\x04!=y\
+\xd3\xa9(\x03\x93\xcf\xd9\x96\xb6\xcc\x08\xb3J\xd9\x1dZ\
+\x0d4\x9a\x01o\xae\xee4\x80'R\x13\x0c\xc6^\x98\
+\x01W\x84\xd9\x98I\x84\x81\x5c\xb3\xee\xd6\xfai\xae\xee\
+4\xf8\x13\xa9\x09\x06b\xcf\xd8\xda\xde\xb4\xa2\x85@Z\
+\x09\xe4\x13\xeew\xb6\xe8;5\x8f\xb9Y\xfd\xc0\xe0\x9f\
+]\x9a\xe4\xc3\xe4S\xab\xe0\x8a\x95\x05}\xe2\x8a\x92\x01\
+\xcf\x7f\x06W1\x0f\xc9?\xdeL\x11\xb7]\x8fTp\
+A\x10\x90\xa4\x7f\xa7\xea\xfa\xc6\x1a\xdfS\xe38\xcf6\
+XB\x9cN\x8dk\x7f\xbd\xc0\x88B\xa0>\x13x\x8b\
+\xb3;Mp\xcaw\xc9\xd1R\xba</-\x81K\x92\
+D\xefP\x1a\x81zm\x06\x1c\xda\x91[\xae\x87\xb48\
+\xbb\xd5\x89\xfa\x0c\xe8\xffs\x8di\x89\xeb\xcc\xa1%P\
+\xd8].\x08;>\x1am\x9a\xfb\x5c\xb5/3\xef\x19\
+\x9b\x1dTA\x14 \xedw6Bfc\x15L%\x03\
+S@\xa5vtt\xa6\x1f@-\x85\xd6\xd67\xd6\xf8\
+\xf4-\xc0f\xe1;@\xc1T\x09D\x80\x1e}\xc4\xe8\
+L?\x82`\xa3\xc5\xd9\x95\xe6/>r\x9c\xab\xe7n\
+\xect\x1bF\xb4%\x88e\x8b\xfa\x9cx\xc7\xd8\xecG\
+C\xaa]\x15\x0d\xf4\x5c{\x8d\xddV\xb0\x8cb\x00v\
+\x80xx\xf9O][y;p\x22Sdr5\x8e\
+\xddV\xc4\xc5S5i\xfe\xd3\xc5\x0e\xca\x8a/\x90X\
+\x9a@\x92\xb6-uJA\x9f\xf8F%\x00P\xd7V\
+>\x0ftf\x9b1\xbf\x1a\xa7\xc8v\x98\xf3'\x9dF\
+\x12%\x96I\xdc\x8b\x87\x97\x7f\xe9/\xa3\xa8|Uf\
+\xb5\xf0\xf4[\x22sC\x06\xbf\xab\xa2\x81n\xcf+l\
+\x82\xddl\x9f\x18=x\x0d\xc9~\xb5d\x86\xa6T\x0e\
+\xe8\xdb\x03\xf0>=x\xcew\xc1.g\xa2#\x13\xf8\
+\xc1~\x98\xc8\xe5X\x00\xaa\x80\xa6|;$\x03p\x93\
+\xdc|.\x1c\xe8\xc7\xe9\xbe\xdb_E\x87\x0e'\xe81\
+\xfc\xef\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x04\xcf\
+\x00\
+\x00\x141x\xda\xc5X\xddS\xe36\x10\x7f\xf7_\xa1\
+\xe6\xe1&\xe9L\x1c>\xae\xd3\x99\xb4\xf4\x06B\xb9\xa3\
+\x93\x9b\x03\x02\xe5\xfa(l%VQ$#\xc9\x04z\
+\xbd\xff\xbd+\xc9vl\xd9\x0e\xa1\xf4\xa8\x9f\xa2\xdd\xf5\
+\xee\xea\xb7\x9f\xceh\x84&\x22}\x94t\x91h\xd4\x9f\
+\x0c\xd0\xde\xce\xee\x8f\xe82!\xe8\x5c\x03g\x99b\xfe\
+\x88\xa6:\x0e\x83\xd1\x08\xcd\xce\x8e?\x0f\xa74\x22\x5c\
+\x91\xe1iL\xb8\xa6sJ\xe4\x18\xe5\xb4\x0b2\x1f\x9e\
+\xeb!\xbc\xb6$2\xa2\x98\xa1O\x17\xe8hv<\xdc\
+\x1fN\x18\xce\x14\x09\x02\xbaL\x85\xd4\xa0\xfc<\xa3\xd1\
+\xadw\x0c'\x82k)\x98Z\xd3\xaf\xc9\xcd\xef\x94\xac\
+|\xc1)~\x14\x99VA\x10\x1c\xa6)\xa3\x11\xd6T\
+\xf0k\xcac\xb1B_\x02\x04\x0f\x8d\xc7he\x09\xf6\
+xO\x15\xbdad\x8c\xb4\xcc\x88\xa5<\x8c\x11\xe5T\
+\x83\x97\x9f\xed\xf9\xb1<\xffa\xcf+\x1a\xeb\xa4\xa4]\
+\x9b\x93\xa5'\xc4`U2>\xd8\xa3\xe5h\xaa\x8d\x85\
+\x95s9\xb4\xc7\xc0r\x96\x84gG\x18\x80\xba\x14\x82\
+\xc1\x8f\xdc\xc7\xc2O\x8e\xef\xe9\xc2^\x01x%\xe7B\
+\xac\xdc5+\xd2\xe6\xc1<J\x84T\xe1\x9c26F\
+)\x96\x10\x87\x9a\x80JqD\xf9b\x8cv\x82\x1a\xdd\
+\x1a\xcf\xb4\x16\xdc\xd3X\xf8q\x83\xa3['\xd0dG\
+\x82\x87Jd2\x82\x1b\xf6\xeed4\x1e12\xd7\xc3\
+\xfd\xbd0\xe5\x8b^C^\xf0\x09\x84\xe5\x96\xc4k<\
+\x16\xe2\x08\xf4\xf7\x07\x0dY\xc21\x84\xa6\x22\x19a\xfe\
+\xde\x0a7D\x1d a*\xc9\x9cHI\xe2k\x17\xa4\
+\x1a\x80a\xb2\x0eI\xf1|}&\x12s!WX\xc6\
+\xdb\x83a\x0b\xe8yh\x9c8\x1b[\x03\x92\xcb\xffo\
+\x98H\xc2\x04\xde\x0a\x92\xc2m#\x0f\x89\x88\xde\xe5 \
+)-\xd2\x02#T\x22G\xe6\x92\xa8\xe49\xd8\xad\xf5\
+\x16\x14\xa3\xb9?@k\x11\xe7l\x0b\xb6\xff\x11Z\xa7\
+\x9a,\xd1\x97.m?4\xc0%\x0f\xfa\x84\x12\x16\xb7\
+`\x9b\xeb0\xe5\x9c\xbf^\xf6(?\x04\x99dVK\
+\x93\xc9\xd3L\x7f$:\x11\xf1\x07\xca\xb5\x1aC\xa7\x0c\
+O\x97\xc9\x95d\x93\x04K\x1ci\x22\xd5'\xce\x1e\xd1\
+\xdf9\xe7\xcc\xba<\x15+\xe8\xd4X5\xcdi\xf0x\
+\x0d'\x18n\x09\xcca\x14\x91TW#\x03r\xe8\x00\
+e\x9a2\xe8OR,\xaf\x14\x91\xa7\xc6\xb7\xbe\xd1\xe7\
+E\xe3\x85\x90>\x95\xaf\xd0o\xdas\xd5]\xedN]\
+\xca~\xef\xbd\xe8\x0d6\xa5\x5cS\xb5y\x00\xc2\x0a\xe2\
+a\x04\x03\x8f\xea\x96dk\x91MhL:$\x9f\x06\
+\xb1\x88\x7f\xd8\x82\xa6\x05\xf4%\xf5\xad\x88\xd6PUj\
+\xfb\xa6W\xbc\xb1M\xed\xb6\x03Yh8\x96\x18\x121\
+\xb43\x17\xee\xdeo\xa5\xff\x82v\x06P\xf3;\xa8\x98\
+\xec9\xfd{\xb4;z\xfb\x04\x18\xafV\xfa\xbb;U\
+\xcb\x95\x9fgR,\xa0\xd1\xa9\xfa\xf8/\xd1Osv\
+\x9d\x93\xcf\xfa\x16\xf4\xcc\xf4-\xa6\x7fh\x0e\xcd4\x17\
+i)p# \xa6\xcb\x86\x88t\xabL.$\x1b7\
+/\x0c}\xc4rAy\xcd\x9c#\xb5k\xf4\xc4+\xb4\
+`S\x88\xf2\xcdj\xbfN\xfd\xcb\xf6\xb2\x94a\x0dC\
+y\x19\x0a\x85\x0e\x0e\x0eP\x0f\xf3X\x0a\x1a\xf7 !\
+\x86\xbb\x90\x11\xc3\xbd\xfakf\x97YH\x91q\xc0\xd6\
+E\xcc\xb3Vn\x84\x9e\xf6\xef\x8cv*T\x0f\xbdy\
+\xd3\xca\x83\xdc\x93\xda\xcbuS\xa6f\xdf\xf2\x02`\xd2\
+\xc1#\xdec\x96y#\xb2\xc8\x0c\xf4\xb3\x11\xafL\xb5\
+\x1a\xb3\xaa\xde]%\xcfKw\xbb\xa0\xad\x94]\xf9\x04\
+\xfe\xe6\xb8)\xec\x13\xc1\xb2%o]<\xa7\xf8\x86\xb0\
+\x96Tt\xfd\xb4\xf7\x1b\xbe\xc7\xb3H\xd2\xd4\x03\xa7\x8e\
+\xfb$!\xb0d\x8a\x87\x8e&\xf4g\xa9\xe4W\xb7\x04\
+\x15\xf2]V\xf3]\xa9\xb5\xf7\x98Wg\x1ak\x023\
+\x90/\xaas\xaa\x00(l\xd83\xdd'*_\x84\x5c\
+39`5\x91x\xb0\xe1^O\x803\x15\x11|\x0e\
+\xc1\x9a\x22\xf1\x82\xbc\x00\x1ff\xf4\xcc\x9c\x9a\xd7A\xa8\
+\xc5\xe27\xc2\xe8\x901\xf8|\x83=\x88 \x0c\x9b\x85\
+R/\xc0\x09\x1b]'\xa0\xea\xd0jz\x1d\xa8<\xa3\
+\xdf4\x95`\x1ak\xa8^\x04\x9f\x069Z\x0e\xb9\xab\
+\x8b\xa9zi~M\x9c\xee\x09\xe6\xee\x22\xe6J\xb0I\
+\xaaWL\xb8.\x17\xfe\x0d\xa6\xb5n\x99\xff\xa1\xe05\
+\xcc\xdc\x89\x92\x06\x8bV\xf9\x85\x7fU\xd9}\xbd\x0e\xea\
+\xad)\xb51\x5c\x88v\x0f\xea\xe2\x9f\x84\x9c\xe7m\x1e\
+\x82O\xddgN\x89\xd4<\xe3\x91YS\xfaf.\x5c\
+\x90\xbb\x8c(=\xf0\xe2H\xe7\xa8\xca\x0ea5\x11r\
+\xa6%\xe8i\xee\x8b\x90CJ0\xe2\x84:_\xfb)\
+hY\x84\xcc_C\x82\x1b\xb7!\xa8\xf0\x9b\x11\xdd\xdc\
+\xf2:\x9byX\x0d!\xda\xd0\x8c\xdfU\xc2\x8b\xec\xa8\
+\xbe\xe2\x91;\xd6,mh\x8b\x1d\xb6\xda\xda\xda\xb6\xd6\
+67\x97\x0e\x83~sx\xd6\xd5\xb6\xac\xc8Mw\xed\
+\xac\xa8m\x1c)J\xe8k\xf0\x0f\x96\x1c\x9e\xf1\
+\x00\x00\x036\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x06bKGD\x00i\x00\xa1\x006za\
+\x0c\x8d\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\
+\xdf\x01\x17\x0f(,\xbb\xcb\xc7\xb8\x00\x00\x02\xc3ID\
+ATX\xc3\xcd\x97\xbfOSQ\x14\xc7?\xf7\xb5\x8b\
+\x93&j\x94\x10\xa1\x03\x1a\xa2\xa5\x96\x18\xbbH\xab\xa2\
+\x18\x194\xb0\xc8\xa4U`\xb0\x81\xf4\x0f\x908)\xc6\
+\x99\xc4\xd4 \x12\x8a\x13L\xc4\x1f\x01!\x10\xa0L\x0d\
+\x11JIc\xca\x22\x18\xa2\x83##\xf4:\xf8^\xf3\
+\xca{}?\x10\x027\xe9\xd0{\xce=\xdf\xef;\xe7\
+{\xcf\xbd\x17\x0ey\x087\xce\xb1d\xa8\x02\x08\x03M\
+@\x03\xe0SM?\x80\x05`\x0aH%\xa2\xe9_\xfb\
+F \x96\x0c\x09\xa0\x11\xe8\x03.:\x8c\x9b\x03\xe2\xc0\
+L\x22\x9a\x96{&\x10K\x86\xce\x03c.\x80\xcd\x88\
+\xb4$\xa2\xe9\xb5r\x0e\x8a\x05\xf8\x03 \xff\x1f\xe0\xa8\
+k\xf3j,\xe7\x04b\xc9P\x170\xb2\x8fZ\x1bQ\
+c\xda\x97@e;r@\xa2oKD\xd3\xa3e\x09\
+\xa85\xcf[E\xa8\xad\x08\xe1\xaf\xbcf\x98\xff\xb3\xb5\
+\xc9\xec\xf7Q'$.\xe85!v\xa9}\xd5\xae\xe6\
+\x8a\xf0\xd0\x11yI\xb0\xea\xa6\xc16\xb5\xfa\x81\xb1\xa5\
+7N\x84\xe9\xd7v\x87^\x03\x8dN\x04W\x90;\x0c\
+\xa6\x9e\x93\xd9\x983\xd8\x9a\xfc\x0fi\xa9\xefr\x22\xcc\
+F3\x11\xf69-\xe4Na\x9b\xf7\xa9\x1e2\x1bs\
+\x08!J~w\xea\x1eq\xbf>f\x17\xa2\x88\xe5\xd1\
+u\xb8\xd7n\xd4$e\x81\xe5\x9f\xb3T\x9e\xa8\xe1\xec\
+q_\x89\xad\xe6L\x10Ex\xc9\xff^,\xb7\xfc\xf4\
+\xd5\xd6\xca\xfe\xc5\xb1\xcd--\x03\xe1\xbdHz\xa7\xb0\
+\xcd\xc0\xfc32\x1bsHY\xda\xf0\x9a\x03O\xb8\x17\
+|j\xb5<\x0c\xe0\xd5\xca\xa7\xb7\xd4W\xdfrE\xe4\
+\xdb\xfa4\xd5\xa7.q\xfc\xd8\xc9\x92\xf9\xbbu\x8f\x91\
+\xb2\xc0\xe7L\xbf\xd9\xb2&`T#\xd0\xa0\xb7tF\
+z\x11B \xa5D\x08a\x92~\xe3|9\xdf\xe6@\
+;\x80\x19\x89\x06}\x06|\xe5k\xfd/\xb5\x1a!k\
+]\x98\xfb6\x07\xda\x91H\xbed\xde\xe9\xdd}\x96g\
+\x813!J[RvC\xd1\x9d\xe7\xc6>\xad\xdb^\
+V\xff\xedl\xe3+\x83\xbb\xbf\xbe\x88\xa9\x95`\x01\xa8\
+\xd5,\x03\xf3=\xae\xbe\xc2\xabxi\xbd\x12/\x8aP\
+_\x82\xf1\x95\xc1r\x22\x5c\xd0\x13\x98\x02:5\xcb\xd2\
+\xfa\xb4cp\x8f\xe2\xa53\xf2\xaad\x07h\xe0\x13\xd9\
+\xa1r\xe0\x1af\xb1\x04\xa9\xbd\xd4O\x03\x0f\x9c\x8b\x18\
+l\x13\xd9!>-\xbf\xb5Z\x9e*\x12P\xefp9\
+\xb7\xe0\x1d\xe1^.W]7\xb4\xe3\xaf\xabI;\xf0\
+\x9cvo\xd4\xef\x82\xb8[\xf0`\xf5\x0d\x83m2;\
+\xcc\xc7\xa5\x84]\x88\xb8\xd9a4\xe3$\x0b\x8a\xf0\xd0\
+\x1e~a\x00\x97R2\x99\x1dvz\x1c\xcf\x1c\xbd\x0b\
+\xc9a]\xc9\x0c\x9dPu\xe8>\x00\xf0\xee\xdd\xe0\x96\
+\xef\x82}\xceD\x9b\x19\xf8\xd1~\x98\xa8\xe5X\x03\xfc\
+\xc0m\x97}\x22\xa7\xae\xf1[\x81\x1f\x89\xc7\xe9\xa1\x8f\
+\xbfgH\x11\xb4\x13s\xe2\x92\x00\x00\x00\x00IEN\
+D\xaeB`\x82\
+"
+
+qt_resource_name = b"\
+\x00\x0b\
+\x00\x90;'\
+\x00s\
+\x00t\x00o\x00p\x00-\x003\x002\x00.\x00p\x00n\x00g\
+\x00\x0e\
+\x07\xd4\xda\x07\
+\x00r\
+\x00e\x00f\x00r\x00e\x00s\x00h\x00-\x003\x002\x00.\x00p\x00n\x00g\
+\x00\x0b\
+\x00\x80-\xa7\
+\x00l\
+\x00e\x00f\x00t\x00-\x003\x002\x00.\x00p\x00n\x00g\
+\x00\x08\
+\x08\x01Z\x5c\
+\x00m\
+\x00a\x00i\x00n\x00.\x00q\x00m\x00l\
+\x00\x0c\
+\x0fz\xe9\xa7\
+\x00r\
+\x00i\x00g\x00h\x00t\x00-\x003\x002\x00.\x00p\x00n\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00>\x00\x00\x00\x00\x00\x01\x00\x00\x07\x9c\
+\x00\x00\x01\x91\x08\xc1\x9ai\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x91\x08\xc1\x9ai\
+\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x02\x83\
+\x00\x00\x01\x91\x08\xc1\x9ai\
+\x00\x00\x00Z\x00\x01\x00\x00\x00\x01\x00\x00\x0a\xdf\
+\x00\x00\x01\x91\x08\xc1\x9ai\
+\x00\x00\x00p\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xb2\
+\x00\x00\x01\x91\x08\xc1\x9ai\
+"
+
+def qInitResources():
+    QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+    QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
index e423ea347e2d30f3eae5383de2c16470ddd58066..cfeb54c7776d285da737eda7877dc0549647893e 100644 (file)
@@ -6,5 +6,8 @@
               "../registerwigglywidget.py",
               "../wigglywidget.cpp",
               "../wigglywidget.h",
-              "../wigglywidget.py"]
+              "../wigglywidget.py",
+              "../CMakeLists.txt",
+              "../bindings.xml"
+      ]
 }
diff --git a/requirements-coin.txt b/requirements-coin.txt
new file mode 100644 (file)
index 0000000..5ee2bac
--- /dev/null
@@ -0,0 +1,12 @@
+pip>=24.2
+setuptools==72.1.0
+importlib_metadata>=6
+importlib_resources>=5.10.2
+packaging>=24
+ordered-set>=3.1.1
+more_itertools>=8.8
+jaraco.text>=3.7
+importlib_metadata>=6
+tomli>=2.0.1
+wheel>=0.43.0
+platformdirs >= 2.6.2
index 7205ef57fee54bcc9c39aaea37c5f9fa456ef233..fd88e2ed54f02c1da5be4f2800d6d7e174bcefad 100644 (file)
@@ -1,12 +1,12 @@
-sphinx==7.2.6
-sphinx-design==0.5.0
+sphinx==7.4.7
+sphinx-design==0.6.0
 sphinx-copybutton==0.5.2
-sphinx-tags==0.3.1
-sphinx-toolbox
-myst-parser==2.0.0
+sphinx-tags==0.4
+sphinx-toolbox==3.7.0
+myst-parser==3.0.1
 # FIXME: Using fork in order to enable the 'collapse_navbar=True'
 # option for the sphinx-theme. Upstream proposal:
 # https://github.com/pradyunsg/furo/pull/748#issuecomment-1895448722
 # furo==2023.9.10
 furo @ git+https://github.com/cmaureir/furo@add_collapse
-graphviz==0.20
+graphviz==0.20.3
index 65380e93b28a6cdedb905a33b25da5359b1d32e8..d6b23bd8cd23e6f32f88043a8c740b2c993292c8 100644 (file)
@@ -1,8 +1,8 @@
 # Build dependencies
-setuptools==69.1.1
-packaging==23.2
-build==1.0.3
-wheel==0.42.0
+setuptools==72.1.0
+packaging==24.1
+build==1.2.1
+wheel==0.43.0
 distro==1.9.0; sys_platform == 'linux'
 patchelf==0.17.2; sys_platform == 'linux'
 # 1.24.4 is the last version that supports Python 3.8
index aa03d13d07389deb996c9b8136e034db59fbb166..aa9ad33aa8130e541e58bdf8263de2a26ffd15c1 100644 (file)
@@ -28,6 +28,7 @@
 
 """
 
+import sys
 import argparse
 import logging
 import traceback
@@ -49,10 +50,20 @@ TOOL_DESCRIPTION = dedent(f"""
                           Linux = .bin
                           """)
 
+HELP_MODE = dedent("""
+                   The mode in which the application is deployed. The options are: onefile,
+                   standalone. The default value is onefile.
+
+                   This options translates to the mode Nuitka uses to create the executable.
+
+                   macOS by default uses the --standalone option.
+                   """)
+
 
 def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False,
          loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False,
-         force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None):
+         force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None,
+         mode: bool = False):
 
     logging.basicConfig(level=loglevel)
     if config_file and not config_file.exists() and not main_file.exists():
@@ -91,7 +102,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
 
     config = DesktopConfig(config_file=config_file, source_file=main_file, python_exe=python.exe,
                            dry_run=dry_run, existing_config_file=config_file_exists,
-                           extra_ignore_dirs=extra_ignore_dirs)
+                           extra_ignore_dirs=extra_ignore_dirs, mode=mode)
 
     # set application name
     if name:
@@ -122,6 +133,15 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
         logging.info(f"[DEPLOY]: Config file {config.config_file} created")
         return
 
+    # If modules contain QtSql and the platform is macOS, then pyside6-deploy will not work
+    # currently. The fix ideally will have to come from Nuitka.
+    # See PYSIDE-2835
+    # TODO: Remove this check once the issue is fixed in Nuitka
+    # Nuitka Issue: https://github.com/Nuitka/Nuitka/issues/3079
+    if "Sql" in config.modules and sys.platform == "darwin":
+        print("[DEPLOY] QtSql Application is not supported on macOS with pyside6-deploy")
+        return
+
     try:
         # create executable
         if not dry_run:
@@ -135,7 +155,8 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
                                                excluded_qml_plugins=config.excluded_qml_plugins,
                                                icon=config.icon,
                                                dry_run=dry_run,
-                                               permissions=config.permissions)
+                                               permissions=config.permissions,
+                                               mode=config.mode)
     except Exception:
         print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}")
     finally:
@@ -182,7 +203,11 @@ if __name__ == "__main__":
 
     parser.add_argument("--extra-modules", type=str, help=HELP_EXTRA_MODULES)
 
+    parser.add_argument("--mode", choices=["onefile", "standalone"], default="onefile",
+                        help=HELP_MODE)
+
     args = parser.parse_args()
 
     main(args.main_file, args.name, args.config_file, args.init, args.loglevel, args.dry_run,
-         args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules)
+         args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules,
+         args.mode)
index a40d0838b4406c0915b022a025688652a20f4d77..3b9bc99fc8d0b0d2e3191fb646ff8ff6145374ab 100644 (file)
@@ -17,6 +17,8 @@ else:
     EXE_FORMAT = ".bin"
 
 DEFAULT_APP_ICON = str((Path(__file__).parent / f"pyside_icon{IMAGE_FORMAT}").resolve())
+DEFAULT_IGNORE_DIRS = ["site-packages", "deployment", ".qtcreator", "build", "dist", "tests"]
+
 IMPORT_WARNING_PYSIDE = (f"[DEPLOY] Found 'import PySide6' in file {0}"
                          ". Use 'from PySide6 import <module>' or pass the module"
                          " needed using --extra-modules command line argument")
index ad818c2ff0787739d5d725a1bb828ff291f58be5..213aefa7e8874831c0145e9723b03d00d5741b39 100644 (file)
@@ -1,6 +1,7 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 import re
+import sys
 import tempfile
 import logging
 import zipfile
@@ -15,6 +16,7 @@ from . import (extract_and_copy_jar, get_wheel_android_arch, find_lib_dependenci
 from .. import (Config, find_pyside_modules, get_all_pyside_modules, MAJOR_VERSION)
 
 ANDROID_NDK_VERSION = "26b"
+ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125"
 ANDROID_DEPLOY_CACHE = Path.home() / ".pyside6_android_deploy"
 
 
@@ -58,6 +60,11 @@ class AndroidConfig(Config):
             else:
                 ndk_path_temp = (ANDROID_DEPLOY_CACHE / "android-ndk"
                                  / f"android-ndk-r{ANDROID_NDK_VERSION}")
+                if sys.platform == "darwin":
+                    ndk_path_temp = (
+                        ANDROID_DEPLOY_CACHE / "android-ndk"
+                        / f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app/Contents/NDK"
+                    )
                 if ndk_path_temp.exists():
                     self.ndk_path = ndk_path_temp
 
@@ -342,7 +349,7 @@ class AndroidConfig(Config):
                 self._dependency_files.append(dependency_file)
 
         logging.info("[DEPLOY] The following dependency files were found: "
-                     f"{*self._dependency_files,}")
+                     f"{*self._dependency_files, }")
 
     def _find_local_libs(self):
         local_libs = set()
index 7d2f5d57523d556e4b732c83f0ea0fa92348242a..16c13a8452b69cb0d4ce12c27938f2474a442cd4 100644 (file)
@@ -1,6 +1,6 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
+import sys
 import logging
 import zipfile
 from dataclasses import dataclass
@@ -91,7 +91,7 @@ def get_llvm_readobj(ndk_path: Path) -> Path:
     '''
     # TODO: Requires change if Windows platform supports Android Deployment or if we
     # support host other than linux-x86_64
-    return (ndk_path / "toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readobj")
+    return (ndk_path / f"toolchains/llvm/prebuilt/{sys.platform}-x86_64/bin/llvm-readobj")
 
 
 def find_lib_dependencies(llvm_readobj: Path, lib_path: Path, used_dependencies: Set[str] = None,
index 3a7e2a2e28aab8215bfee76b710f8c7b070b7881..eb18a2cd31a1542b661e80bb0fb58afdbe160e16 100644 (file)
@@ -4,8 +4,11 @@
 import json
 import subprocess
 import sys
+import shutil
+import tempfile
 from pathlib import Path
-from typing import List
+from functools import lru_cache
+
 
 """
 All utility functions for deployment
@@ -37,24 +40,32 @@ def run_command(command, dry_run: bool, fetch_output: bool = False):
     return command_str, output
 
 
-def run_qmlimportscanner(qml_files: List[Path], dry_run: bool):
+@lru_cache
+def run_qmlimportscanner(qml_files: tuple[Path], dry_run: bool):
     """
-        Runs pyside6-qmlimportscanner to find all the imported qml modules
+        Runs pyside6-qmlimportscanner to find all the imported qml modules in project_dir
     """
-    if not qml_files:
-        return []
-
     qml_modules = []
-    cmd = ["pyside6-qmlimportscanner", "-qmlFiles"]
-    cmd.extend([str(qml_file) for qml_file in qml_files])
-
-    if dry_run:
-        run_command(command=cmd, dry_run=True)
-
-    # we need to run qmlimportscanner during dry_run as well to complete the
-    # command being run by nuitka
-    _, json_string = run_command(command=cmd, dry_run=False, fetch_output=True)
-    json_string = json_string.decode("utf-8")
-    json_array = json.loads(json_string)
-    qml_modules = [item['name'] for item in json_array if item['type'] == "module"]
+    # Create a temporary directory to copy all the .qml_files
+    # TODO: Modify qmlimportscanner code in qtdeclarative to include a flag to ignore directories
+    # Then, this copy into a temporary directory can be avoided
+    # See 36b425ea8bf36d47694ea69fa7d129b6d5a2ca2d in gerrit
+    with tempfile.TemporaryDirectory() as temp_dir:
+        temp_path = Path(temp_dir)
+        # Copy only files with .qml suffix
+        for qml_file in qml_files:
+            if qml_file.suffix == ".qml":
+                shutil.copy2(qml_file.resolve(), temp_path / qml_file.name)
+
+        cmd = ["pyside6-qmlimportscanner", "-rootPath", str(temp_path)]
+
+        if dry_run:
+            run_command(command=cmd, dry_run=True)
+
+        # Run qmlimportscanner during dry_run as well to complete the command being run by nuitka
+        _, json_string = run_command(command=cmd, dry_run=False, fetch_output=True)
+        json_string = json_string.decode("utf-8")
+        json_array = json.loads(json_string)
+        qml_modules = [item['name'] for item in json_array if item['type'] == "module"]
+
     return qml_modules
index d59dd92ad353dc06757d51026a98d243e2042ae2..d5d4bc34f9fd1818a51fdce1ec415e6c1e5b0017 100644 (file)
@@ -4,14 +4,16 @@
 import sys
 import configparser
 import logging
+import tempfile
 import warnings
 from configparser import ConfigParser
 from typing import List
 from pathlib import Path
+from enum import Enum
 
 from project import ProjectData
-from . import (DEFAULT_APP_ICON, find_pyside_modules, find_permission_categories,
-               QtDependencyReader, run_qmlimportscanner)
+from . import (DEFAULT_APP_ICON, DEFAULT_IGNORE_DIRS, find_pyside_modules,
+               find_permission_categories, QtDependencyReader, run_qmlimportscanner)
 
 # Some QML plugins like QtCore are excluded from this list as they don't contribute much to
 # executable size. Excluding them saves the extra processing of checking for them in files
@@ -41,8 +43,26 @@ class BaseConfig:
 
     def update_config(self):
         logging.info(f"[DEPLOY] Creating {self.config_file}")
-        with open(self.config_file, "w+") as config_file:
-            self.parser.write(config_file, space_around_delimiters=True)
+
+        # This section of code is done to preserve the formatting of the original deploy.spec
+        # file where there is blank line before the comments
+        with tempfile.NamedTemporaryFile('w+', delete=False) as temp_file:
+            self.parser.write(temp_file, space_around_delimiters=True)
+            temp_file_path = temp_file.name
+
+        # Read the temporary file and write back to the original file with blank lines before
+        # comments
+        with open(temp_file_path, 'r') as temp_file, open(self.config_file, 'w') as config_file:
+            previous_line = None
+            for line in temp_file:
+                if (line.lstrip().startswith('#') and previous_line is not None
+                   and not previous_line.lstrip().startswith('#')):
+                    config_file.write('\n')
+                config_file.write(line)
+                previous_line = line
+
+        # Clean up the temporary file
+        Path(temp_file_path).unlink()
 
     def set_value(self, section: str, key: str, new_value: str, raise_warning: bool = True):
         try:
@@ -141,19 +161,26 @@ class Config(BaseConfig):
 
     def set_or_fetch(self, config_property_val, config_property_key, config_property_group="app"):
         """
-        Write to config_file if 'config_property_key' is known without config_file
-        Fetch and return from config_file if 'config_property_key' is unknown, but
-        config_file exists
-        Otherwise, raise an exception
+        Set the configuration value if provided, otherwise fetch the existing value.
+        Raise an exception if neither is available.
+
+        :param value: The value to set if provided.
+        :param key: The configuration key.
+        :param group: The configuration group (default is "app").
+        :return: The configuration value.
+        :raises RuntimeError: If no value is provided and no existing value is found.
         """
+        existing_value = self.get_value(config_property_group, config_property_key)
+
         if config_property_val:
             self.set_value(config_property_group, config_property_key, str(config_property_val))
             return config_property_val
-        elif self.get_value(config_property_group, config_property_key):
-            return self.get_value(config_property_group, config_property_key)
+        elif existing_value:
+            return existing_value
         else:
             raise RuntimeError(
-                f"[DEPLOY] No {config_property_key} specified in config file or as cli option"
+                f"[DEPLOY] No value for {config_property_key} specified in config file or as cli"
+                " option"
             )
 
     @property
@@ -260,24 +287,12 @@ class Config(BaseConfig):
             qml_files_temp = None
             if self.source_file and self.python_path:
                 if not self.qml_files:
-                    qml_files_temp = list(self.source_file.parent.glob("**/*.qml"))
-
-                # add all QML files, excluding the ones shipped with installed PySide6
-                # The QML files shipped with PySide6 gets added if venv is used,
-                # because of recursive glob
-                if self.python_path.parent.parent == self.source_file.parent:
-                    # python venv path is inside the main source dir
-                    qml_files_temp = list(
-                        set(qml_files_temp) - set(self.python_path.parent.parent.rglob("*.qml"))
-                    )
-
-                if len(qml_files_temp) > 500:
-                    if "site-packages" in str(qml_files_temp[-1]):
-                        raise RuntimeError(
-                            "You are including a lot of QML files from a local virtual env."
-                            " This can lead to errors in deployment."
-                        )
-                    else:
+                    # filter out files from DEFAULT_IGNORE_DIRS
+                    qml_files_temp = [file for file in self.source_file.parent.glob("**/*.qml")
+                                      if all(part not in file.parts for part in
+                                             DEFAULT_IGNORE_DIRS)]
+
+                    if len(qml_files_temp) > 500:
                         warnings.warn(
                             "You seem to include a lot of QML files. This can lead to errors in "
                             "deployment."
@@ -286,6 +301,7 @@ class Config(BaseConfig):
                 if qml_files_temp:
                     extra_qml_files = [Path(file) for file in qml_files_temp]
                     self.qml_files.extend(extra_qml_files)
+
         if self.qml_files:
             self.set_value(
                 "qt",
@@ -327,7 +343,8 @@ class Config(BaseConfig):
 
     def _find_and_set_excluded_qml_plugins(self):
         if self.qml_files:
-            self.qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files,
+            self.qml_modules = set(run_qmlimportscanner(qml_files=tuple(self.qml_files),
+                                                        # tuple is needed to make it hashable
                                                         dry_run=self.dry_run))
             self.excluded_qml_plugins = EXCLUDED_QML_PLUGINS.difference(self.qml_modules)
 
@@ -359,7 +376,7 @@ class Config(BaseConfig):
         """Identify if QtQuick is used in QML files and add them as dependency
         """
         extra_modules = []
-        if not self.qml_modules:
+        if not self.qml_modules and self.qml_files:
             self.qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files,
                                                         dry_run=self.dry_run))
 
@@ -375,8 +392,13 @@ class Config(BaseConfig):
 class DesktopConfig(Config):
     """Wrapper class around pysidedeploy.spec, but specific to Desktop deployment
     """
+    class NuitkaMode(Enum):
+        ONEFILE = "onefile"
+        STANDALONE = "standalone"
+
     def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool,
-                 existing_config_file: bool = False, extra_ignore_dirs: List[str] = None):
+                 existing_config_file: bool = False, extra_ignore_dirs: List[str] = None,
+                 mode: str = "onefile"):
         super().__init__(config_file, source_file, python_exe, dry_run, existing_config_file,
                          extra_ignore_dirs)
         self.dependency_reader = QtDependencyReader(dry_run=self.dry_run)
@@ -402,6 +424,12 @@ class DesktopConfig(Config):
             else:
                 self._find_and_set_permissions()
 
+        self._mode = self.NuitkaMode.ONEFILE
+        if self.get_value("nuitka", "mode") == self.NuitkaMode.STANDALONE.value:
+            self._mode = self.NuitkaMode.STANDALONE
+        elif mode == self.NuitkaMode.STANDALONE.value:
+            self.mode = self.NuitkaMode.STANDALONE
+
     @property
     def qt_plugins(self):
         return self._qt_plugins
@@ -420,6 +448,15 @@ class DesktopConfig(Config):
         self._permissions = permissions
         self.set_value("nuitka", "macos.permissions", ",".join(permissions))
 
+    @property
+    def mode(self):
+        return self._mode
+
+    @mode.setter
+    def mode(self, mode: NuitkaMode):
+        self._mode = mode
+        self.set_value("nuitka", "mode", mode.value)
+
     def _find_dependent_qt_modules(self):
         """
         Given pysidedeploy_config.modules, find all the other dependent Qt modules.
index 57f99eca13404595eed61c644a0e3e2a21920e29..47d543cd357325859c3603efd78e7fe3236b7612 100644 (file)
@@ -25,9 +25,7 @@ icon =
 python_path =
 
 # python packages to install
-# ordered-set: increase compile time performance of nuitka packaging
-# zstandard: provides final executable size optimization
-packages = Nuitka==2.3.2
+packages = Nuitka==2.4.8
 
 # buildozer: for deploying Android application
 android_packages = buildozer==1.5.0,cython==0.29.33
@@ -65,6 +63,9 @@ plugins =
 # eg: NSCameraUsageDescription:CameraAccess
 macos.permissions =
 
+# mode of using Nuitka. Accepts standalone or onefile. Default is onefile.
+mode = onefile
+
 # (str) specify any extra nuitka arguments
 # eg: extra_args = --show-modules --follow-stdlib
 extra_args = --quiet --noinclude-qt-translations
index 2d5b188d35735c2188afb2bea9224d9b2e825303..a145d3adab009815626bf5f98581524fa777b1c3 100644 (file)
@@ -14,7 +14,7 @@ from pathlib import Path
 from typing import List, Set
 from functools import lru_cache
 
-from . import IMPORT_WARNING_PYSIDE, run_command
+from . import IMPORT_WARNING_PYSIDE, DEFAULT_IGNORE_DIRS, run_command
 
 
 @lru_cache(maxsize=None)
@@ -22,7 +22,7 @@ def get_py_files(project_dir: Path, extra_ignore_dirs: List[Path] = None, projec
     """Finds and returns all the Python files in the project
     """
     py_candidates = []
-    ignore_dirs = ["__pycache__", "env", "venv", "deployment"]
+    ignore_dirs = ["__pycache__", *DEFAULT_IGNORE_DIRS]
 
     if project_data:
         py_candidates = project_data.python_files
@@ -135,6 +135,7 @@ def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None,
     all_modules = set()
     mod_pattern = re.compile("PySide6.Qt(?P<mod_name>.*)")
 
+    @lru_cache
     def pyside_module_imports(py_file: Path):
         modules = []
         try:
@@ -217,12 +218,15 @@ class QtDependencyReader:
         """
         Finds the path to the Qt libs directory inside PySide6 package installation
         """
+        # PYSIDE-2785 consider dist-packages for Debian based systems
         for possible_site_package in site.getsitepackages():
-            if possible_site_package.endswith("site-packages"):
+            if possible_site_package.endswith(("site-packages", "dist-packages")):
                 self.pyside_install_dir = Path(possible_site_package) / "PySide6"
+                if self.pyside_install_dir.exists():
+                    break
 
         if not self.pyside_install_dir:
-            print("Unable to find site-packages. Exiting ...")
+            print("Unable to find where PySide6 is installed. Exiting ...")
             sys.exit(-1)
 
         if sys.platform == "win32":
index e8b05e9904e40facb3fd92ab034d42d2d1fb02c2..1e0e2712a8e58fd9a08953a78b7d6b3a7cff4d40 100644 (file)
@@ -7,7 +7,7 @@ import sys
 from pathlib import Path
 
 from . import EXE_FORMAT
-from .config import Config
+from .config import Config, DesktopConfig
 
 
 def config_option_exists():
@@ -61,17 +61,21 @@ def create_config_file(dry_run: bool = False, config_file: Path = None, main_fil
     return config_file
 
 
-def finalize(config: Config):
+def finalize(config: DesktopConfig):
     """
         Copy the executable into the final location
         For Android deployment, this is done through buildozer
     """
-    generated_exec_path = config.generated_files_path / (config.source_file.stem + EXE_FORMAT)
+    dist_format = EXE_FORMAT
+    if config.mode == DesktopConfig.NuitkaMode.STANDALONE and sys.platform != "darwin":
+        dist_format = ".dist"
+
+    generated_exec_path = config.generated_files_path / (config.source_file.stem + dist_format)
     if generated_exec_path.exists() and config.exe_dir:
-        if sys.platform == "darwin":
-            shutil.copytree(generated_exec_path, config.exe_dir / (config.title + EXE_FORMAT),
+        if sys.platform == "darwin" or config.mode == DesktopConfig.NuitkaMode.STANDALONE:
+            shutil.copytree(generated_exec_path, config.exe_dir / (config.title + dist_format),
                             dirs_exist_ok=True)
         else:
             shutil.copy(generated_exec_path, config.exe_dir)
         print("[DEPLOY] Executed file created in "
-              f"{str(config.exe_dir / (config.source_file.stem + EXE_FORMAT))}")
+              f"{str(config.exe_dir / (config.source_file.stem + dist_format))}")
index ac9a83f3f1355167bf5dbb5d3055151b4bd4a5b7..7eba0755524006e53ad6d8abd1510c7a92b7dfeb 100644 (file)
@@ -1,13 +1,18 @@
 # Copyright (C) 2022 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
+# enables to use typehints for classes that has not been defined yet or imported
+# used for resolving circular imports
+from __future__ import annotations
 import logging
 import os
+import shlex
 import sys
 from pathlib import Path
 from typing import List
 
 from . import MAJOR_VERSION, run_command
+from .config import DesktopConfig
 
 
 class Nuitka:
@@ -50,12 +55,44 @@ class Nuitka:
         else:
             return "--macos-app-icon"
 
-    def create_executable(self, source_file: Path, extra_args: str, qml_files: List[Path],
-                          qt_plugins: List[str], excluded_qml_plugins: List[str], icon: str,
-                          dry_run: bool, permissions: List[str]):
+    def _create_windows_command(self, source_file: Path, command: list):
+        """
+        Special case for Windows where the command length is limited to 8191 characters.
+        """
+
+        # if the platform is windows and the command is more than 8191 characters, the command
+        # will fail with the error message "The command line is too long". To avoid this, we will
+        # we will move the source_file to the intermediate source file called deploy_main.py, and
+        # include the Nuitka options direcly in the main file as mentioned in
+        # https://nuitka.net/user-documentation/user-manual.html#nuitka-project-options
+
+        # convert command into a format recognized by Nuitka when written to the main file
+        # the first item is ignore because it is 'python -m nuitka'
+        nuitka_comment_options = []
+        for command_entry in command[4:]:
+            nuitka_comment_options.append(f"# nuitka-project: {command_entry}")
+        nuitka_comment_options_str = "\n".join(nuitka_comment_options)
+        nuitka_comment_options_str += "\n"
+
+        # read the content of the source file
+        new_source_content = (nuitka_comment_options_str
+                              + Path(source_file).read_text(encoding="utf-8"))
+
+        # create and write back the new source content to deploy_main.py
+        new_source_file = source_file.parent / "deploy_main.py"
+        new_source_file.write_text(new_source_content, encoding="utf-8")
+
+        return new_source_file
+
+    def create_executable(self, source_file: Path, extra_args: str, qml_files: list[Path],
+                          qt_plugins: list[str], excluded_qml_plugins: list[str], icon: str,
+                          dry_run: bool, permissions: list[str],
+                          mode: DesktopConfig.NuitkaMode):
         qt_plugins = [plugin for plugin in qt_plugins if plugin not in self.qt_plugins_to_ignore]
-        extra_args = extra_args.split()
 
+        extra_args = shlex.split(extra_args)
+
+        # macOS uses the --standalone option by default to create an app bundle
         if sys.platform == "darwin":
             # create an app bundle
             extra_args.extend(["--standalone", "--macos-create-app-bundle"])
@@ -63,7 +100,7 @@ class Nuitka:
             for permission in permissions:
                 extra_args.append(permission_pattern.format(permission=permission))
         else:
-            extra_args.append("--onefile")
+            extra_args.append(f"--{mode.value}")
 
         qml_args = []
         if qml_files:
@@ -114,5 +151,19 @@ class Nuitka:
             qt_plugins_str = ",".join(qt_plugins)
             command.append(f"--include-qt-plugins={qt_plugins_str}")
 
+        long_command = False
+        if sys.platform == "win32" and len(" ".join(str(cmd) for cmd in command)) > 7000:
+            logging.info("[DEPLOY] Nuitka command too long for Windows. "
+                         "Copying the contents of main Python file to an intermediate "
+                         "deploy_main.py file")
+            long_command = True
+            new_source_file = self._create_windows_command(source_file=source_file, command=command)
+            command = self.nuitka + [os.fspath(new_source_file)]
+
         command_str, _ = run_command(command=command, dry_run=dry_run)
+
+        # if deploy_main.py exists, delete it after the command is run
+        if long_command:
+            os.remove(source_file.parent / "deploy_main.py")
+
         return command_str
index 0970f9974f02bf7566bd498ea2902a81f7a07342..4a02e07bb64ea124fb4324c06ba9b558d6422bab 100644 (file)
@@ -109,7 +109,7 @@ def _parse_call_args(call: ast.Call):
     """Parse arguments of a Signal call/Slot decorator (type list)."""
     result: Arguments = []
     for n, arg in enumerate(call.args):
-        par_name = f"a{n+1}"
+        par_name = f"a{n + 1}"
         par_type = _parse_pyside_type(arg)
         result.append({"name": par_name, "type": par_type})
     return result
index 3706a29859300032b1e14f4a8ed6fe5ef164d5f9..d540147d6a8000bfc3231fe3a30298f4b4df8c7e 100644 (file)
@@ -243,13 +243,14 @@ class Project:
             return
 
         source_files = self.project.python_files + self.project.ui_files
-        cmd_prefix = [LUPDATE_CMD] + [p.name for p in source_files]
+        project_dir = self.project.project_file.parent
+        cmd_prefix = [LUPDATE_CMD] + [os.fspath(p.relative_to(project_dir)) for p in source_files]
         cmd_prefix.append("-ts")
         for ts_file in self.project.ts_files:
             if requires_rebuild(source_files, ts_file):
                 cmd = cmd_prefix
                 cmd.append(ts_file.name)
-                run_command(cmd, cwd=self.project.project_file.parent)
+                run_command(cmd, cwd=project_dir)
 
 
 if __name__ == "__main__":
index b369be8a2a7b284dd6d615bcce7093e48b0a1edc..f68b3185e9d33bbc600c4a472c39b15024481326 100644 (file)
@@ -214,8 +214,9 @@ def deploy():
 
 
 def android_deploy():
-    if not sys.platform == "linux":
-        print("pyside6-android-deploy only works from a Linux host")
+    if sys.platform == "win32":
+        print("pyside6-android-deploy only works from a Unix host and not a Windows host",
+              file=sys.stderr)
     else:
         android_requirements_file = Path(__file__).parent / "requirements-android.txt"
         with open(android_requirements_file, 'r', encoding='UTF-8') as file:
index 8f6fb6eca201fef48017f2a128cd39b8842f815e..83abf0d44db67af3239c694b329cc1042d35c2b4 100644 (file)
@@ -1,5 +1,5 @@
 set(pyside_MAJOR_VERSION "6")
 set(pyside_MINOR_VERSION "7")
-set(pyside_MICRO_VERSION "2")
+set(pyside_MICRO_VERSION "3")
 set(pyside_PRE_RELEASE_VERSION_TYPE "")
 set(pyside_PRE_RELEASE_VERSION "")
index 60d1846d1912255fee047ce5966fe2fb25b7b01e..d284e4f6a5e9c2b4bf3fa6929f90d46180ea9226 100644 (file)
@@ -1,5 +1,6 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from __future__ import annotations
 
 from .events import (
     QAsyncioEventLoopPolicy, QAsyncioEventLoop, QAsyncioHandle, QAsyncioTimerHandle
@@ -7,8 +8,9 @@ from .events import (
 from .futures import QAsyncioFuture
 from .tasks import QAsyncioTask
 
+from typing import Coroutine, Any
+
 import asyncio
-import typing
 
 __all__ = [
     "QAsyncioEventLoopPolicy", "QAsyncioEventLoop",
@@ -17,12 +19,33 @@ __all__ = [
 ]
 
 
-def run(coro: typing.Optional[typing.Coroutine] = None,
-        keep_running: bool = True,
-        quit_qapp: bool = True, *,
-        handle_sigint: bool = False,
-        debug: typing.Optional[bool] = None) -> typing.Any:
-    """Run the QtAsyncio event loop."""
+def run(coro: Coroutine | None = None,
+        keep_running: bool = True, quit_qapp: bool = True, *, handle_sigint: bool = False,
+        debug: bool | None = None) -> Any:
+    """
+    Run the QtAsyncio event loop.
+
+    If there is no instance of a QCoreApplication, QGuiApplication or
+    QApplication yet, a new instance of QCoreApplication is created.
+
+    :param coro:            The coroutine to run. Optional if keep_running is
+                            True.
+    :param keep_running:    If True, QtAsyncio (the asyncio event loop) will
+                            continue running after the coroutine finished, or
+                            run "forever" if no coroutine was provided.
+                            If False, QtAsyncio will stop after the
+                            coroutine finished. A coroutine must be provided if
+                            this argument is set to False.
+    :param quit_qapp:       If True, the QCoreApplication will quit when
+                            QtAsyncio (the asyncio event loop) stops.
+                            If False, the QCoreApplication will remain active
+                            after QtAsyncio stops, and can continue to be used.
+    :param handle_sigint:   If True, the SIGINT signal will be handled by the
+                            event loop, causing it to stop.
+    :param debug:           If True, the event loop will run in debug mode.
+                            If False, the event loop will run in normal mode.
+                            If None, the default behavior is used.
+    """
 
     # Event loop policies are expected to be deprecated with Python 3.13, with
     # subsequent removal in Python 3.15. At that point, part of the current
@@ -49,7 +72,7 @@ def run(coro: typing.Optional[typing.Coroutine] = None,
             ret = asyncio.run(coro, debug=debug)
         else:
             exc = RuntimeError(
-                "QtAsyncio was set to keep running after the coroutine "
+                "QtAsyncio was set not to keep running after the coroutine "
                 "finished, but no coroutine was provided.")
 
     asyncio.set_event_loop_policy(default_policy)
index 86b89014a94e9796d779c976e7076828af73217c..36c7fea979094c80bf511687838338895f903feb 100644 (file)
@@ -1,5 +1,6 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from __future__ import annotations
 
 from PySide6.QtCore import (QCoreApplication, QDateTime, QDeadlineTimer,
                             QEventLoop, QObject, QTimer, QThread, Slot)
@@ -7,6 +8,8 @@ from PySide6.QtCore import (QCoreApplication, QDateTime, QDeadlineTimer,
 from . import futures
 from . import tasks
 
+from typing import Any, Callable
+
 import asyncio
 import collections.abc
 import concurrent.futures
@@ -16,7 +19,6 @@ import os
 import signal
 import socket
 import subprocess
-import typing
 import warnings
 
 __all__ = [
@@ -40,7 +42,7 @@ class QAsyncioExecutorWrapper(QObject):
     the actual callable for the executor into this new event loop.
     """
 
-    def __init__(self, func: typing.Callable, *args: typing.Tuple) -> None:
+    def __init__(self, func: Callable, *args: tuple) -> None:
         super().__init__()
         self._loop: QEventLoop
         self._func = func
@@ -63,7 +65,10 @@ class QAsyncioExecutorWrapper(QObject):
         self._loop = QEventLoop()
         asyncio.events._set_running_loop(self._loop)
 
-        QTimer.singleShot(0, self._loop, lambda: self._cb())
+        # The do() function will always be executed from the new executor
+        # thread and never from outside, so using the overload without the
+        # context argument is sufficient.
+        QTimer.singleShot(0, lambda: self._cb())
 
         self._loop.exec()
         if self._exception is not None:
@@ -87,16 +92,10 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy):
     https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553
     """
     def __init__(self,
-                 application: typing.Optional[QCoreApplication] = None,
                  quit_qapp: bool = True,
                  handle_sigint: bool = False) -> None:
         super().__init__()
-        if application is None:
-            if QCoreApplication.instance() is None:
-                application = QCoreApplication()
-            else:
-                application = QCoreApplication.instance()
-        self._application: QCoreApplication = application  # type: ignore[assignment]
+        self._application = QCoreApplication.instance() or QCoreApplication()
 
         # Configure whether the QCoreApplication at the core of QtAsyncio
         # should be shut down when asyncio finishes. A special case where one
@@ -105,7 +104,7 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy):
         # this instance is shut down every time.
         self._quit_qapp = quit_qapp
 
-        self._event_loop: typing.Optional[asyncio.AbstractEventLoop] = None
+        self._event_loop: asyncio.AbstractEventLoop | None = None
 
         if handle_sigint:
             signal.signal(signal.SIGINT, signal.SIG_DFL)
@@ -115,7 +114,7 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy):
             self._event_loop = QAsyncioEventLoop(self._application, quit_qapp=self._quit_qapp)
         return self._event_loop
 
-    def set_event_loop(self, loop: typing.Optional[asyncio.AbstractEventLoop]) -> None:
+    def set_event_loop(self, loop: asyncio.AbstractEventLoop | None) -> None:
         self._event_loop = loop
 
     def new_event_loop(self) -> asyncio.AbstractEventLoop:
@@ -191,7 +190,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         self._quit_from_outside = False
 
         # A set of all asynchronous generators that are currently running.
-        self._asyncgens: typing.Set[collections.abc.AsyncGenerator] = set()
+        self._asyncgens: set[collections.abc.AsyncGenerator] = set()
 
         # Starting with Python 3.11, this must be an instance of
         # ThreadPoolExecutor.
@@ -202,14 +201,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         # asynchonrous generator raises an exception when closed, and two, if
         # an exception is raised during the execution of a task. Currently, the
         # default exception handler just prints the exception to the console.
-        self._exception_handler: typing.Optional[typing.Callable] = self.default_exception_handler
+        self._exception_handler: Callable | None = self.default_exception_handler
 
         # The task factory, if set with set_task_factory(). Otherwise, a new
         # task is created with the QAsyncioTask constructor.
-        self._task_factory: typing.Optional[typing.Callable] = None
+        self._task_factory: Callable | None = None
 
         # The future that is currently being awaited with run_until_complete().
-        self._future_to_complete: typing.Optional[futures.QAsyncioFuture] = None
+        self._future_to_complete: futures.QAsyncioFuture | None = None
 
         self._debug = bool(os.getenv("PYTHONASYNCIODEBUG", False))
 
@@ -228,7 +227,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         future.get_loop().stop()
 
     def run_until_complete(self,
-                           future: futures.QAsyncioFuture) -> typing.Any:  # type: ignore[override]
+                           future: futures.QAsyncioFuture) -> Any:  # type: ignore[override]
         if self.is_closed():
             raise RuntimeError("Event loop is closed")
         if self.is_running():
@@ -322,7 +321,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         self._asyncgens.clear()
 
     async def shutdown_default_executor(self,  # type: ignore[override]
-                                        timeout: typing.Union[int, float, None] = None) -> None:
+                                        timeout: int | float | None = None) -> None:
         shutdown_successful = False
         if timeout is not None:
             deadline_timer = QDeadlineTimer(int(timeout * 1000))
@@ -347,51 +346,46 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
 
     # Scheduling callbacks
 
-    def _call_soon_impl(self, callback: typing.Callable, *args: typing.Any,
-                        context: typing.Optional[contextvars.Context] = None,
-                        is_threadsafe: typing.Optional[bool] = False) -> asyncio.Handle:
+    def _call_soon_impl(self, callback: Callable, *args: Any,
+                        context: contextvars.Context | None = None,
+                        is_threadsafe: bool | None = False) -> asyncio.Handle:
         return self._call_later_impl(0, callback, *args, context=context,
                                      is_threadsafe=is_threadsafe)
 
-    def call_soon(self, callback: typing.Callable, *args: typing.Any,
-                  context: typing.Optional[contextvars.Context] = None) -> asyncio.Handle:
+    def call_soon(self, callback: Callable, *args: Any,
+                  context: contextvars.Context | None = None) -> asyncio.Handle:
         return self._call_soon_impl(callback, *args, context=context, is_threadsafe=False)
 
-    def call_soon_threadsafe(self, callback: typing.Callable, *args: typing.Any,
-                             context:
-                             typing.Optional[contextvars.Context] = None) -> asyncio.Handle:
+    def call_soon_threadsafe(self, callback: Callable, *args: Any,
+                             context: contextvars.Context | None = None) -> asyncio.Handle:
         if self.is_closed():
             raise RuntimeError("Event loop is closed")
         if context is None:
             context = contextvars.copy_context()
         return self._call_soon_impl(callback, *args, context=context, is_threadsafe=True)
 
-    def _call_later_impl(self, delay: typing.Union[int, float],
-                         callback: typing.Callable, *args: typing.Any,
-                         context: typing.Optional[contextvars.Context] = None,
-                         is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle:
+    def _call_later_impl(self, delay: int | float, callback: Callable, *args: Any,
+                         context: contextvars.Context | None = None,
+                         is_threadsafe: bool | None = False) -> asyncio.TimerHandle:
         if not isinstance(delay, (int, float)):
             raise TypeError("delay must be an int or float")
         return self._call_at_impl(self.time() + delay, callback, *args, context=context,
                                   is_threadsafe=is_threadsafe)
 
-    def call_later(self, delay: typing.Union[int, float],
-                   callback: typing.Callable, *args: typing.Any,
-                   context: typing.Optional[contextvars.Context] = None) -> asyncio.TimerHandle:
+    def call_later(self, delay: int | float, callback: Callable, *args: Any,
+                   context: contextvars.Context | None = None) -> asyncio.TimerHandle:
         return self._call_later_impl(delay, callback, *args, context=context, is_threadsafe=False)
 
-    def _call_at_impl(self, when: typing.Union[int, float],
-                      callback: typing.Callable, *args: typing.Any,
-                      context: typing.Optional[contextvars.Context] = None,
-                      is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle:
+    def _call_at_impl(self, when: int | float, callback: Callable, *args: Any,
+                      context: contextvars.Context | None = None,
+                      is_threadsafe: bool | None = False) -> asyncio.TimerHandle:
         """ All call_at() and call_later() methods map to this method. """
         if not isinstance(when, (int, float)):
             raise TypeError("when must be an int or float")
         return QAsyncioTimerHandle(when, callback, args, self, context, is_threadsafe=is_threadsafe)
 
-    def call_at(self, when: typing.Union[int, float],
-                callback: typing.Callable, *args: typing.Any,
-                context: typing.Optional[contextvars.Context] = None) -> asyncio.TimerHandle:
+    def call_at(self, when: int | float, callback: Callable, *args: Any,
+                context: contextvars.Context | None = None) -> asyncio.TimerHandle:
         return self._call_at_impl(when, callback, *args, context=context, is_threadsafe=False)
 
     def time(self) -> float:
@@ -403,9 +397,9 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         return futures.QAsyncioFuture(loop=self)
 
     def create_task(self,  # type: ignore[override]
-                    coro: typing.Union[collections.abc.Generator, collections.abc.Coroutine],
-                    *, name: typing.Optional[str] = None,
-                    context: typing.Optional[contextvars.Context] = None) -> tasks.QAsyncioTask:
+                    coro: collections.abc.Generator | collections.abc.Coroutine,
+                    *, name: str | None = None,
+                    context: contextvars.Context | None = None) -> tasks.QAsyncioTask:
         if self._task_factory is None:
             task = tasks.QAsyncioTask(coro, loop=self, name=name, context=context)
         else:
@@ -414,12 +408,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
 
         return task
 
-    def set_task_factory(self, factory: typing.Optional[typing.Callable]) -> None:
+    def set_task_factory(self, factory: Callable | None) -> None:
         if factory is not None and not callable(factory):
             raise TypeError("The task factory must be a callable or None")
         self._task_factory = factory
 
-    def get_task_factory(self) -> typing.Optional[typing.Callable]:
+    def get_task_factory(self) -> Callable | None:
         return self._task_factory
 
     # Opening network connections
@@ -432,14 +426,15 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
             ssl_handshake_timeout=None,
             ssl_shutdown_timeout=None,
             happy_eyeballs_delay=None, interleave=None):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.create_connection() is not implemented yet")
 
     async def create_datagram_endpoint(self, protocol_factory,
                                        local_addr=None, remote_addr=None, *,
                                        family=0, proto=0, flags=0,
                                        reuse_address=None, reuse_port=None,
                                        allow_broadcast=None, sock=None):
-        raise NotImplementedError
+        raise NotImplementedError(
+            "QAsyncioEventLoop.create_datagram_endpoint() is not implemented yet")
 
     async def create_unix_connection(
             self, protocol_factory, path=None, *,
@@ -447,7 +442,8 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
             server_hostname=None,
             ssl_handshake_timeout=None,
             ssl_shutdown_timeout=None):
-        raise NotImplementedError
+        raise NotImplementedError(
+            "QAsyncioEventLoop.create_unix_connection() is not implemented yet")
 
     # Creating network servers
 
@@ -459,7 +455,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
             ssl_handshake_timeout=None,
             ssl_shutdown_timeout=None,
             start_serving=True):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.create_server() is not implemented yet")
 
     async def create_unix_server(
             self, protocol_factory, path=None, *,
@@ -467,20 +463,21 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
             ssl_handshake_timeout=None,
             ssl_shutdown_timeout=None,
             start_serving=True):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.create_unix_server() is not implemented yet")
 
     async def connect_accepted_socket(
             self, protocol_factory, sock,
             *, ssl=None,
             ssl_handshake_timeout=None,
             ssl_shutdown_timeout=None):
-        raise NotImplementedError
+        raise NotImplementedError(
+            "QAsyncioEventLoop.connect_accepted_socket() is not implemented yet")
 
     # Transferring files
 
     async def sendfile(self, transport, file, offset=0, count=None,
                        *, fallback=True):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sendfile() is not implemented yet")
 
     # TLS Upgrade
 
@@ -489,82 +486,83 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
                         server_hostname=None,
                         ssl_handshake_timeout=None,
                         ssl_shutdown_timeout=None):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.start_tls() is not implemented yet")
 
     # Watching file descriptors
 
     def add_reader(self, fd, callback, *args):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.add_reader() is not implemented yet")
 
     def remove_reader(self, fd):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.remove_reader() is not implemented yet")
 
     def add_writer(self, fd, callback, *args):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.add_writer() is not implemented yet")
 
     def remove_writer(self, fd):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.remove_writer() is not implemented yet")
 
     # Working with socket objects directly
 
     async def sock_recv(self, sock, nbytes):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_recv() is not implemented yet")
 
     async def sock_recv_into(self, sock, buf):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_recv_into() is not implemented yet")
 
     async def sock_recvfrom(self, sock, bufsize):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_recvfrom() is not implemented yet")
 
     async def sock_recvfrom_into(self, sock, buf, nbytes=0):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_recvfrom_into() is not implemented yet")
 
     async def sock_sendall(self, sock, data):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_sendall() is not implemented yet")
 
     async def sock_sendto(self, sock, data, address):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_sendto() is not implemented yet")
 
     async def sock_connect(self, sock, address):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_connect() is not implemented yet")
 
     async def sock_accept(self, sock):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_accept() is not implemented yet")
 
     async def sock_sendfile(self, sock, file, offset=0, count=None, *,
                             fallback=None):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.sock_sendfile() is not implemented yet")
 
     # DNS
 
     async def getaddrinfo(self, host, port, *,
                           family=0, type=0, proto=0, flags=0):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.getaddrinfo() is not implemented yet")
 
     async def getnameinfo(self, sockaddr, flags=0):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.getnameinfo() is not implemented yet")
 
     # Working with pipes
 
     async def connect_read_pipe(self, protocol_factory, pipe):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.connect_read_pipe() is not implemented yet")
 
     async def connect_write_pipe(self, protocol_factory, pipe):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.connect_write_pipe() is not implemented yet")
 
     # Unix signals
 
     def add_signal_handler(self, sig, callback, *args):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.add_signal_handler() is not implemented yet")
 
     def remove_signal_handler(self, sig):
-        raise NotImplementedError
+        raise NotImplementedError(
+            "QAsyncioEventLoop.remove_signal_handler() is not implemented yet")
 
     # Executing code in thread or process pools
 
     def run_in_executor(self,
-                        executor: typing.Optional[concurrent.futures.ThreadPoolExecutor],
-                        func: typing.Callable, *args: typing.Tuple) -> asyncio.futures.Future:
+                        executor: concurrent.futures.ThreadPoolExecutor | None,
+                        func: Callable, *args: tuple) -> asyncio.futures.Future:
         if self.is_closed():
             raise RuntimeError("Event loop is closed")
         if executor is None:
@@ -582,28 +580,27 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         )
 
     def set_default_executor(self,
-                             executor: typing.Optional[
-                                 concurrent.futures.ThreadPoolExecutor]) -> None:
+                             executor: concurrent.futures.ThreadPoolExecutor | None) -> None:
         if not isinstance(executor, concurrent.futures.ThreadPoolExecutor):
             raise TypeError("The executor must be a ThreadPoolExecutor")
         self._default_executor = executor
 
     # Error Handling API
 
-    def set_exception_handler(self, handler: typing.Optional[typing.Callable]) -> None:
+    def set_exception_handler(self, handler: Callable | None) -> None:
         if handler is not None and not callable(handler):
             raise TypeError("The handler must be a callable or None")
         self._exception_handler = handler
 
-    def get_exception_handler(self) -> typing.Optional[typing.Callable]:
+    def get_exception_handler(self) -> Callable | None:
         return self._exception_handler
 
-    def default_exception_handler(self, context: typing.Dict[str, typing.Any]) -> None:
+    def default_exception_handler(self, context: dict[str, Any]) -> None:
         # TODO
         if context["message"]:
             print(context["message"])
 
-    def call_exception_handler(self, context: typing.Dict[str, typing.Any]) -> None:
+    def call_exception_handler(self, context: dict[str, Any]) -> None:
         if self._exception_handler is not None:
             self._exception_handler(context)
 
@@ -624,14 +621,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               **kwargs):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.subprocess_exec() is not implemented yet")
 
     async def subprocess_shell(self, protocol_factory, cmd, *,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                **kwargs):
-        raise NotImplementedError
+        raise NotImplementedError("QAsyncioEventLoop.subprocess_shell() is not implemented yet")
 
 
 class QAsyncioHandle():
@@ -646,9 +643,9 @@ class QAsyncioHandle():
         CANCELLED = enum.auto()
         DONE = enum.auto()
 
-    def __init__(self, callback: typing.Callable, args: typing.Tuple,
-                 loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context],
-                 is_threadsafe: typing.Optional[bool] = False) -> None:
+    def __init__(self, callback: Callable, args: tuple,
+                 loop: QAsyncioEventLoop, context: contextvars.Context | None,
+                 is_threadsafe: bool | None = False) -> None:
         self._callback = callback
         self._args = args
         self._loop = loop
@@ -663,7 +660,7 @@ class QAsyncioHandle():
     def _start(self) -> None:
         self._schedule_event(self._timeout, lambda: self._cb())
 
-    def _schedule_event(self, timeout: int, func: typing.Callable) -> None:
+    def _schedule_event(self, timeout: int, func: Callable) -> None:
         # Do not schedule events from asyncio when the app is quit from outside
         # the event loop, as this would cause events to be enqueued after the
         # event loop was destroyed.
@@ -703,13 +700,17 @@ class QAsyncioHandle():
 
 
 class QAsyncioTimerHandle(QAsyncioHandle, asyncio.TimerHandle):
-    def __init__(self, when: float, callback: typing.Callable, args: typing.Tuple,
-                 loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context],
-                 is_threadsafe: typing.Optional[bool] = False) -> None:
+    def __init__(self, when: float, callback: Callable, args: tuple,
+                 loop: QAsyncioEventLoop, context: contextvars.Context | None,
+                 is_threadsafe: bool | None = False) -> None:
         QAsyncioHandle.__init__(self, callback, args, loop, context, is_threadsafe)
 
         self._when = when
         time = self._loop.time()
+
+        # PYSIDE-2644: Timeouts should be rounded up or down instead of only up
+        # as happens with int(). Otherwise, a timeout of e.g. 0.9 would be
+        # handled as 0, where 1 would be more appropriate.
         self._timeout = round(max(self._when - time, 0) * 1000)
 
         QAsyncioHandle._start(self)
index cbb005fc92ee9d85e3d739212225501b75947cea..6b4415490cdf0e42ed04e4aa9b713d8570dcff4a 100644 (file)
@@ -1,12 +1,14 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from __future__ import annotations
 
 from . import events
 
+from typing import Any, Callable
+
 import asyncio
 import contextvars
 import enum
-import typing
 
 
 class QAsyncioFuture():
@@ -23,8 +25,8 @@ class QAsyncioFuture():
         DONE_WITH_RESULT = enum.auto()
         DONE_WITH_EXCEPTION = enum.auto()
 
-    def __init__(self, *, loop: typing.Optional["events.QAsyncioEventLoop"] = None,
-                 context: typing.Optional[contextvars.Context] = None) -> None:
+    def __init__(self, *, loop: "events.QAsyncioEventLoop | None" = None,
+                 context: contextvars.Context | None = None) -> None:
         self._loop: "events.QAsyncioEventLoop"
         if loop is None:
             self._loop = asyncio.events.get_event_loop()  # type: ignore[assignment]
@@ -33,13 +35,13 @@ class QAsyncioFuture():
         self._context = context
 
         self._state = QAsyncioFuture.FutureState.PENDING
-        self._result: typing.Any = None
-        self._exception: typing.Optional[BaseException] = None
+        self._result: Any = None
+        self._exception: BaseException | None = None
 
-        self._cancel_message: typing.Optional[str] = None
+        self._cancel_message: str | None = None
 
         # List of callbacks that are called when the future is done.
-        self._callbacks: typing.List[typing.Callable] = list()
+        self._callbacks: list[Callable] = list()
 
     def __await__(self):
         if not self.done():
@@ -51,13 +53,13 @@ class QAsyncioFuture():
 
     __iter__ = __await__
 
-    def _schedule_callbacks(self, context: typing.Optional[contextvars.Context] = None):
+    def _schedule_callbacks(self, context: contextvars.Context | None = None):
         """ A future can optionally have callbacks that are called when the future is done. """
         for cb in self._callbacks:
             self._loop.call_soon(
                 cb, self, context=context if context else self._context)
 
-    def result(self) -> typing.Union[typing.Any, Exception]:
+    def result(self) -> Any | Exception:
         if self._state == QAsyncioFuture.FutureState.DONE_WITH_RESULT:
             return self._result
         if self._state == QAsyncioFuture.FutureState.DONE_WITH_EXCEPTION and self._exception:
@@ -69,7 +71,7 @@ class QAsyncioFuture():
                 raise asyncio.CancelledError
         raise asyncio.InvalidStateError
 
-    def set_result(self, result: typing.Any) -> None:
+    def set_result(self, result: Any) -> None:
         self._result = result
         self._state = QAsyncioFuture.FutureState.DONE_WITH_RESULT
         self._schedule_callbacks()
@@ -85,20 +87,20 @@ class QAsyncioFuture():
     def cancelled(self) -> bool:
         return self._state == QAsyncioFuture.FutureState.CANCELLED
 
-    def add_done_callback(self, cb: typing.Callable, *,
-                          context: typing.Optional[contextvars.Context] = None) -> None:
+    def add_done_callback(self, cb: Callable, *,
+                          context: contextvars.Context | None = None) -> None:
         if self.done():
             self._loop.call_soon(
                 cb, self, context=context if context else self._context)
         else:
             self._callbacks.append(cb)
 
-    def remove_done_callback(self, cb: typing.Callable) -> int:
+    def remove_done_callback(self, cb: Callable) -> int:
         original_len = len(self._callbacks)
         self._callbacks = [_cb for _cb in self._callbacks if _cb != cb]
         return original_len - len(self._callbacks)
 
-    def cancel(self, msg: typing.Optional[str] = None) -> bool:
+    def cancel(self, msg: str | None = None) -> bool:
         if self.done():
             return False
         self._state = QAsyncioFuture.FutureState.CANCELLED
@@ -106,7 +108,7 @@ class QAsyncioFuture():
         self._schedule_callbacks()
         return True
 
-    def exception(self) -> typing.Optional[BaseException]:
+    def exception(self) -> BaseException | None:
         if self._state == QAsyncioFuture.FutureState.CANCELLED:
             raise asyncio.CancelledError
         if self.done():
index 7edc15093284ca1734c94b77f654201984e4b1d8..be1809d5c3aa85e14fc99d5b192041430c61ff89 100644 (file)
@@ -1,23 +1,24 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from __future__ import annotations
 
 from . import events
 from . import futures
 
+from typing import Any
+
 import asyncio
 import collections.abc
 import concurrent.futures
 import contextvars
-import typing
 
 
 class QAsyncioTask(futures.QAsyncioFuture):
     """ https://docs.python.org/3/library/asyncio-task.html """
 
-    def __init__(self, coro: typing.Union[collections.abc.Generator, collections.abc.Coroutine], *,
-                 loop: typing.Optional["events.QAsyncioEventLoop"] = None,
-                 name: typing.Optional[str] = None,
-                 context: typing.Optional[contextvars.Context] = None) -> None:
+    def __init__(self, coro: collections.abc.Generator | collections.abc.Coroutine, *,
+                 loop: "events.QAsyncioEventLoop | None" = None, name: str | None = None,
+                 context: contextvars.Context | None = None) -> None:
         super().__init__(loop=loop, context=context)
 
         self._coro = coro  # The coroutine for which this task was created.
@@ -25,15 +26,18 @@ class QAsyncioTask(futures.QAsyncioFuture):
 
         # The task creates a handle for its coroutine. The handle enqueues the
         # task's step function as its callback in the event loop.
-        self._handle = self._loop.call_soon(self._step, context=self._context)
+        self._loop.call_soon(self._step, context=self._context)
 
         # The task step function executes the coroutine until it finishes,
         # raises an exception or returns a future. If a future was returned,
-        # the task will await its completion (or exception).
-        self._future_to_await: typing.Optional[asyncio.Future] = None
+        # the task will await its completion (or exception). If the task is
+        # cancelled while it awaits a future, this future must also be
+        # cancelled in order for the cancellation to be successful.
+        self._future_to_await: asyncio.Future | None = None
 
-        self._cancelled = False
-        self._cancel_message: typing.Optional[str] = None
+        self._cancelled = False  # PYSIDE-2644; see _step
+        self._cancel_count = 0
+        self._cancel_message: str | None = None
 
         # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support
         asyncio._register_task(self)  # type: ignore[arg-type]
@@ -53,17 +57,16 @@ class QAsyncioTask(futures.QAsyncioFuture):
     class QtTaskApiMisuseError(Exception):
         pass
 
-    def set_result(self, result: typing.Any) -> None:  # type: ignore[override]
+    def set_result(self, result: Any) -> None:  # type: ignore[override]
         # This function is not inherited from the Future APIs.
         raise QAsyncioTask.QtTaskApiMisuseError("Tasks cannot set results")
 
-    def set_exception(self, exception: typing.Any) -> None:  # type: ignore[override]
+    def set_exception(self, exception: Any) -> None:  # type: ignore[override]
         # This function is not inherited from the Future APIs.
         raise QAsyncioTask.QtTaskApiMisuseError("Tasks cannot set exceptions")
 
     def _step(self,
-              exception_or_future: typing.Union[
-                  BaseException, futures.QAsyncioFuture, None] = None) -> None:
+              exception_or_future: BaseException | futures.QAsyncioFuture | None = None) -> None:
         """
         The step function is the heart of a task. It is scheduled in the event
         loop repeatedly, executing the coroutine "step" by "step" (i.e.,
@@ -77,6 +80,10 @@ class QAsyncioTask(futures.QAsyncioFuture):
         result = None
         self._future_to_await = None
 
+        if self._cancelled:
+            exception_or_future = asyncio.CancelledError(self._cancel_message)
+            self._cancelled = False
+
         if asyncio.futures.isfuture(exception_or_future):
             try:
                 exception_or_future.result()
@@ -113,11 +120,17 @@ class QAsyncioTask(futures.QAsyncioFuture):
                 # called again.
                 result.add_done_callback(
                     self._step, context=self._context)  # type: ignore[arg-type]
+
+                # The task will await the completion (or exception) of this
+                # future. If the task is cancelled while it awaits a future,
+                # this future must also be cancelled.
                 self._future_to_await = result
+
                 if self._cancelled:
-                    # If the task was cancelled, then a new future should be
-                    # cancelled as well. Otherwise, in some scenarios like
-                    # a loop inside the task and with bad timing, if the new
+                    # PYSIDE-2644: If the task was cancelled at this step and a
+                    # new future was created to be awaited, then it should be
+                    # cancelled as well. Otherwise, in some scenarios like a
+                    # loop inside the task and with bad timing, if the new
                     # future is not cancelled, the task would continue running
                     # in this loop despite having been cancelled. This bad
                     # timing can occur especially if the first future finishes
@@ -135,10 +148,13 @@ class QAsyncioTask(futures.QAsyncioFuture):
             asyncio._leave_task(self._loop, self)  # type: ignore[arg-type]
 
             if self._exception:
+                message = str(self._exception)
+                if message == "None":
+                    message = ""
+                else:
+                    message = "An exception occurred during task execution"
                 self._loop.call_exception_handler({
-                    "message": (str(self._exception) if self._exception
-                                else "An exception occurred during task "
-                                "execution"),
+                    "message": message,
                     "exception": self._exception,
                     "task": self,
                     "future": (exception_or_future
@@ -152,7 +168,7 @@ class QAsyncioTask(futures.QAsyncioFuture):
                 # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support
                 asyncio._unregister_task(self)  # type: ignore[arg-type]
 
-    def get_stack(self, *, limit=None) -> typing.List[typing.Any]:
+    def get_stack(self, *, limit=None) -> list[Any]:
         # TODO
         raise NotImplementedError("QtTask.get_stack is not implemented")
 
@@ -160,7 +176,7 @@ class QAsyncioTask(futures.QAsyncioFuture):
         # TODO
         raise NotImplementedError("QtTask.print_stack is not implemented")
 
-    def get_coro(self) -> typing.Union[collections.abc.Generator, collections.abc.Coroutine]:
+    def get_coro(self) -> collections.abc.Generator | collections.abc.Coroutine:
         return self._coro
 
     def get_name(self) -> str:
@@ -169,22 +185,22 @@ class QAsyncioTask(futures.QAsyncioFuture):
     def set_name(self, value) -> None:
         self._name = str(value)
 
-    def cancel(self, msg: typing.Optional[str] = None) -> bool:
+    def cancel(self, msg: str | None = None) -> bool:
         if self.done():
             return False
+        self._cancel_count += 1
         self._cancel_message = msg
-        self._handle.cancel()
         if self._future_to_await is not None:
             # A task that is awaiting a future must also cancel this future in
             # order for the cancellation to be successful.
             self._future_to_await.cancel(msg)
-        self._cancelled = True
+        self._cancelled = True  # PYSIDE-2644; see _step
         return True
 
-    def uncancel(self) -> None:
-        # TODO
-        raise NotImplementedError("QtTask.uncancel is not implemented")
+    def uncancel(self) -> int:
+        if self._cancel_count > 0:
+            self._cancel_count -= 1
+        return self._cancel_count
 
-    def cancelling(self) -> bool:
-        # TODO
-        raise NotImplementedError("QtTask.cancelling is not implemented")
+    def cancelling(self) -> int:
+        return self._cancel_count
index 19a50848ff11ed2cc4dda9657458575e75d8f73b..624c29a9ed82cb6dadb8210463488843e5fcc9d4 100644 (file)
 
   <value-type name="QModelRoleData"/>
 
-  <object-type name="QAbstractItemModel">
+  <!-- Register meta type for QML properties -->
+  <object-type name="QAbstractItemModel" qt-register-metatype="yes">
     <enum-type name="CheckIndexOption" flags="CheckIndexOptions"/>
     <enum-type name="LayoutChangeHint"/>
      <!-- This function was replaced by a added function -->
 
         </inject-documentation>
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchild-2"/>
-      <modify-argument index="return">
+      <modify-argument index="return" pyi-type="Optional[PlaceHolderType]">
         <parent index="this" action="add"/>
       </modify-argument>
     </add-function>
         Like the method *findChild*, the first parameter should be the child's type.
         </inject-documentation>
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchildren"/>
-        <modify-argument index="return">
+        <modify-argument index="return" pyi-type="Iterable[PlaceHolderType]">
             <parent index="this" action="add"/>
         </modify-argument>
     </add-function>
     <add-function signature="findChildren(PyTypeObject*@type@,const QRegularExpression&amp;@pattern@,Qt::FindChildOptions@options@=Qt::FindChildrenRecursively)"
                   return-type="PySequence*" >
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchildren"/>
-        <modify-argument index="return">
+        <modify-argument index="return" pyi-type="Iterable[PlaceHolderType]">
             <parent index="this" action="add"/>
         </modify-argument>
     </add-function>
     <add-function signature="__msetitem__">
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qbytearray-msetitem"/>
     </add-function>
+    <modify-function signature="fromRawData(const char*, qsizetype)">
+      <modify-argument index="1" pyi-type="str"/>
+    </modify-function>
   </value-type>
   <primitive-type name="QByteArrayView" view-on="QByteArray">
     <conversion-rule>
index 99e0789d1326364975de25e6dac7c9d1404d29c7..c868b6c885eba57281bf6b6eb6d4c88e75be1958 100644 (file)
@@ -313,6 +313,15 @@ set(QtGui_private_include_dirs
     ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS}
     ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS})
 
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Android")
+    if (QT_FEATURE_opengles2)
+        # add openGL ES 2.0
+        find_package(GLESv2 REQUIRED)
+    else()
+        message(FATAL_ERROR "QtGui requires OpenGL ES 2.0 on Android")
+    endif()
+endif()
+
 configure_file("${QtGui_SOURCE_DIR}/QtGui_global.post.h.in"
                "${QtGui_BINARY_DIR}/QtGui_global.post.h" @ONLY)
 
index 2791f695a1655841f60b81e8d197c0040d76c4d1..cd7a2ba6819e4d115af05a23d40e5988aa58fadd 100644 (file)
         <enum-type name="PlaybackState" since="6.1"/>
         <enum-type name="Error"/>
         <enum-type name="Loops" python-type="IntEnum" since="6.2.3"/>
+        <modify-function signature="setSource(QUrl)" allow-thread="true"/>
     </object-type>
     <!-- see qtmultimedia/5773f7214c7430a98dea3974c0597cb3ee0ea7f5 might reappear in 6.3
     <object-type name="QMediaPlaylist"/>
index 4dc7c9b0a9ff5f16d49a918c8db4011a5394621b..10ce14fda35b49323021283031e212aac3458e0a 100644 (file)
             <define-ownership class="target" owner="default"/>
           </modify-argument>
         </modify-function>
-        <modify-function signature="get(const QNetworkRequest&amp;)" allow-thread="yes"/>
-        <modify-function signature="post(const QNetworkRequest &amp;,QIODevice*)" allow-thread="yes"/>
-        <modify-function signature="post(const QNetworkRequest &amp;,const QByteArray &amp;)" allow-thread="yes"/>
-        <modify-function signature="put(const QNetworkRequest &amp;,QIODevice*)" allow-thread="yes"/>
-        <modify-function signature="put(const QNetworkRequest &amp;,const QByteArray &amp;)" allow-thread="yes"/>
-        <modify-function signature="sendCustomRequest(const QNetworkRequest &amp;,const QByteArray &amp;,QIODevice*)" allow-thread="yes" since="4.7"/>
+        <modify-function signature="head(QNetworkRequest)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+            <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="get(QNetworkRequest)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+             <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="get(QNetworkRequest,QIODevice*)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+             <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="get(QNetworkRequest,QByteArray)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+             <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="post(QNetworkRequest,QIODevice*)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+            <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="post(QNetworkRequest,QByteArray)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+            <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="put(QNetworkRequest,QIODevice*)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+            <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="put(QNetworkRequest,QByteArray)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+            <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="sendCustomRequest(QNetworkRequest,QByteArray,QIODevice*)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+            <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
+        <modify-function signature="sendCustomRequest(QNetworkRequest,QByteArray,QByteArray)" allow-thread="yes">
+          <modify-argument index="0"> <!-- Suppress return value heuristics -->
+            <define-ownership class="target" owner="default"/>
+          </modify-argument>
+        </modify-function>
         <modify-function signature="setCache(QAbstractNetworkCache*)">
             <modify-argument index="1">
                 <define-ownership class="target" owner="c++"/>
index fdd2b148353c5d78de4f2b52c92b5af619690558..365d26d6869b8e87f0f1a93f10606b04b9461e44 100644 (file)
         <value-type name="MultiplexValueRange"/>
     </value-type>
     <value-type name="QCanUniqueIdDescription"/>
-    <object-type name="QModbusClient"/>
+    <object-type name="QModbusClient">
+        <modify-function signature="sendReadRequest(QModbusDataUnit,int)">
+            <modify-argument index="0"> <!-- Suppress return value heuristics -->
+                <define-ownership class="target" owner="default"/>
+            </modify-argument>
+        </modify-function>
+        <modify-function signature="sendWriteRequest(QModbusDataUnit,int)">
+            <modify-argument index="0"> <!-- Suppress return value heuristics -->
+                <define-ownership class="target" owner="default"/>
+            </modify-argument>
+        </modify-function>
+        <modify-function signature="sendReadWriteRequest(QModbusDataUnit,QModbusDataUnit,int)">
+            <modify-argument index="0"> <!-- Suppress return value heuristics -->
+                <define-ownership class="target" owner="default"/>
+            </modify-argument>
+        </modify-function>
+        <modify-function signature="sendRawRequest(QModbusRequest,int)">
+            <modify-argument index="0"> <!-- Suppress return value heuristics -->
+                <define-ownership class="target" owner="default"/>
+            </modify-argument>
+        </modify-function>
+    </object-type>
     <value-type name="QModbusDataUnit">
         <enum-type name="RegisterType"/>
     </value-type>
index 0e29f240f65d55a8f01cd91f4325f0eeb1c0fcac..97bb4c40bd1acb111b28fb5bd5458f8768a03c0c 100644 (file)
@@ -179,6 +179,17 @@ s1.addTransition(button.clicked, s1h)&lt;/code>
       </modify-argument>
     </modify-function>
 
+    <modify-function signature="postEvent(QEvent*,QStateMachine::EventPriority)">
+      <modify-argument index="1">
+        <define-ownership owner="c++"/>
+      </modify-argument>
+    </modify-function>
+    <modify-function signature="postDelayedEvent(QEvent*,int)">
+      <modify-argument index="1">
+        <define-ownership owner="c++"/>
+      </modify-argument>
+    </modify-function>
+
     <add-function signature="configuration()" return-type="QSet&lt;QAbstractState*&gt;">
         <inject-code class="target" position="beginning" file="../glue/qtstatemachine.cpp"
                      snippet="qstatemachine-configuration"/>
diff --git a/sources/pyside6/PySide6/QtWebView/CMakeLists.txt b/sources/pyside6/PySide6/QtWebView/CMakeLists.txt
new file mode 100644 (file)
index 0000000..158c720
--- /dev/null
@@ -0,0 +1,37 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+project(QtWebView)
+
+set(QtWebView_Src
+${QtWebView_GEN_DIR}/qtwebview_wrapper.cpp
+# module is always needed
+${QtWebView_GEN_DIR}/qtwebview_module_wrapper.cpp
+)
+
+set(QtWebView_include_dirs ${QtWebView_SOURCE_DIR}
+                           ${QtWebView_BINARY_DIR}
+                           ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS}
+                           ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS}
+                           ${Qt${QT_MAJOR_VERSION}WebView_INCLUDE_DIRS}
+                           ${libpyside_SOURCE_DIR}
+                           ${QtGui_GEN_DIR}
+                           ${QtCore_GEN_DIR}
+                           ${QtWebView_GEN_DIR})
+
+set(QtWebView_libraries pyside6
+                        ${Qt${QT_MAJOR_VERSION}WebView_LIBRARIES})
+
+set(QtWebView_deps QtGui)
+
+# for Windows and Linux, QtWebView depends on QtWebEngine to render content
+if ((WIN32 OR UNIX) AND NOT APPLE)
+    list(APPEND QtWebView_deps QtWebEngineCore QtWebEngineQuick)
+endif()
+
+create_pyside_module(NAME QtWebView
+                     INCLUDE_DIRS QtWebView_include_dirs
+                     LIBRARIES QtWebView_libraries
+                     DEPS QtWebView_deps
+                     TYPESYSTEM_PATH QtWebView_SOURCE_DIR
+                     SOURCES QtWebView_Src)
diff --git a/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml b/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml
new file mode 100644 (file)
index 0000000..1983ed8
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+-->
+<typesystem package="PySide6.QtWebView"
+            namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE">
+    <load-typesystem name="QtCore/typesystem_core.xml" generate ="no"/>
+    <namespace-type name="QtWebView"/>
+</typesystem>
index 0669c18f65022a63335dcab2e0218be50c467cdd..8d1adb8c5f2c87e54def21ad9aeadb33168fdc34 100644 (file)
     </value-type>
 
     <modify-function signature="getLayoutPosition(QLayout*,int*,QFormLayout::ItemRole*)const">
-        <modify-argument index="0">
-            <replace-type modified-type="PyObject"/>
-        </modify-argument>
         <modify-argument index="2">
             <remove-argument/>
         </modify-argument>
         <modify-argument index="3">
             <remove-argument/>
         </modify-argument>
+        <modify-argument index="return" pyi-type="Tuple[int, PySide6.QtWidgets.QFormLayout.ItemRole]">
+            <replace-type modified-type="PyTuple"/>
+        </modify-argument>
         <inject-code class="target" position="beginning" file="../glue/qtwidgets.cpp" snippet="qformlayout-fix-args" />
     </modify-function>
     <modify-function signature="getWidgetPosition(QWidget*,int*,QFormLayout::ItemRole*)const">
index d0a4ecc3700868858c0c706e4085fc04f8a3d81d..e8c107bcdf52a403d33e5d33f5e14bbbfee3a630 100644 (file)
@@ -4,9 +4,7 @@ from pathlib import Path
 from textwrap import dedent
 
 # __all__ is also corrected below.
-__all__ = list("Qt" + body for body in
-    "@all_module_shortnames@"
-    .split(";"))
+__all__ = [@init_modules@]
 __version__ = "@FINAL_PACKAGE_VERSION@"
 __version_info__ = (@BINDING_API_MAJOR_VERSION@, @BINDING_API_MINOR_VERSION@, @BINDING_API_MICRO_VERSION@, "@BINDING_API_PRE_RELEASE_VERSION_TYPE@", "@BINDING_API_PRE_RELEASE_VERSION@")
 
index 0c206db7265f01bfbe9a780f0e2ebddcb8224e32..60332d70a4e3be2e18ddecec203cbf562614e595 100644 (file)
@@ -2071,7 +2071,7 @@ if ((classMethod && (count > 2)) || (!classMethod && (count > 1))) {
 
 bool arg_qpermission = (classMethod && (count == 2)) || (!classMethod && (count == 1));
 
-auto callback = [callable, count, arg_qpermission](const QPermission &permission) -> void
+auto callback = [callable, arg_qpermission](const QPermission &permission) -> void
 {
     Shiboken::GilState state;
     if (arg_qpermission) {
index 1e434f9f68377b3a21eae2f051b64149b8831b12..6e03323ddc130928db2ddd4daf39bad7de1c7c64 100644 (file)
@@ -127,7 +127,9 @@ macro(create_pyside_module)
         list(APPEND shiboken_include_dir_list ${${module_ADDITIONAL_INCLUDE_DIRS}})
     endif()
 
-    # Transform the path separators into something shiboken understands.
+    # Remove duplicates and transform the path separators into something
+    # shiboken understands.
+    list(REMOVE_DUPLICATES shiboken_include_dir_list)
     make_path(shiboken_include_dirs ${shiboken_include_dir_list})
 
     set(force_process_system_include_paths_list "")
@@ -194,6 +196,35 @@ macro(create_pyside_module)
         --lean-headers
         --api-version=${SUPPORTED_QT_VERSION})
 
+    # check if building for Android with a macOS host
+    # This is not needed for Linux because OpenGLES2 development binaries in
+    # linux can be installed by installing 'libgles2-mesa-dev' package which
+    # comes as a default requirement for building PySide6. As such for
+    # cross-compiling in linux, we use the clang compiler from the installed
+    # libclang itself.
+    if(CMAKE_ANDROID_ARCH_LLVM_TRIPLE AND CMAKE_HOST_APPLE)
+        message(STATUS "Building for Android with arch ${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}")
+        list(APPEND shiboken_command "--clang-option=--target=${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}")
+
+        # CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX does not contain the ANDROID_PLATFORM i.e. it ends with
+        # the form 'aarch64-linux-android-'. Remove the last '-' and add the corresponding clang
+        # based on ANDROID_PLATFORM making it 'aarch64-linux-android26-clang++'
+
+        # Get the length of the string
+        string(LENGTH "${CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX}" _length)
+
+        # Subtract 1 from the length to get the characters till '-'
+        math(EXPR _last_index "${_length} - 1")
+
+        # Get the substring from the start to the character before the last one
+        string(SUBSTRING "${CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX}" 0 "${_last_index}"
+               SHIBOKEN_ANDROID_COMPILER_PREFIX)
+
+        # use the compiler from the Android NDK
+        list(APPEND shiboken_command
+            "--compiler-path=${SHIBOKEN_ANDROID_COMPILER_PREFIX}${CMAKE_ANDROID_API}-clang++")
+    endif()
+
     if(CMAKE_HOST_APPLE)
         set(shiboken_framework_include_dir_list ${QT_FRAMEWORK_INCLUDE_DIR})
         make_path(shiboken_framework_include_dirs ${shiboken_framework_include_dir_list})
@@ -311,6 +342,10 @@ macro(create_pyside_module)
                               ${generate_pyi_options})
         add_dependencies("${module_NAME}_pyi" ${module_NAME})
 
+        if(TARGET shibokenmodule)
+            add_dependencies("${module_NAME}_pyi" shibokenmodule)
+        endif()
+
         install(FILES "${CMAKE_CURRENT_BINARY_DIR}/../${module_NAME}.pyi"
                 DESTINATION "${PYTHON_SITE_PACKAGES}/PySide6")
     endif()
index 23ceda6bd466af39a596ecf1db444fe5ac1e91d1..ba066c9e684902b392c3dea958ecc502682cb061 100644 (file)
@@ -130,6 +130,11 @@ macro(collect_optional_modules)
     endif()
     list(APPEND ALL_OPTIONAL_MODULES WebChannel WebEngineCore WebEngineWidgets
          WebEngineQuick WebSockets HttpServer)
+    # for Windows and Linux, QtWebView depends on QtWebEngine to render content
+    if ((WIN32 OR UNIX) AND NOT APPLE AND Qt${QT_MAJOR_VERSION}WebEngineCore_FOUND AND
+        Qt${QT_MAJOR_VERSION}WebEngineQuick_FOUND)
+        list(APPEND ALL_OPTIONAL_MODULES WebView)
+    endif()
     list(APPEND ALL_OPTIONAL_MODULES 3DCore 3DRender 3DInput 3DLogic 3DAnimation 3DExtras)
 endmacro()
 
@@ -138,13 +143,18 @@ macro(check_os)
     set(ENABLE_MAC "0")
     set(ENABLE_WIN "0")
 
-    if(CMAKE_HOST_APPLE)
-        set(ENABLE_MAC "1")
-    elseif(CMAKE_HOST_WIN32)
-        set(ENABLE_WIN "1")
-        set(ENABLE_UNIX "0")
-    elseif(NOT CMAKE_HOST_UNIX)
-        message(FATAL_ERROR "OS not supported")
+    # check if Android, if so, set ENABLE_UNIX=1
+    # this is needed to avoid including the wrapper specific to macOS when building for Android
+    # from a macOS host
+    if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android")
+        if(CMAKE_HOST_APPLE)
+            set(ENABLE_MAC "1")
+        elseif(CMAKE_HOST_WIN32)
+            set(ENABLE_WIN "1")
+            set(ENABLE_UNIX "0")
+        elseif(NOT CMAKE_HOST_UNIX)
+            message(FATAL_ERROR "OS not supported")
+        endif()
     endif()
 endmacro()
 
@@ -178,6 +188,12 @@ macro(remove_skipped_modules)
             list(REMOVE_ITEM DISABLED_MODULES ${m})
         endif()
     endforeach()
+
+    # Special list of modules for the __init__.py.in file
+    foreach(im ${all_module_shortnames})
+        list(APPEND init_modules "\"Qt${im}\"")
+    endforeach()
+    string(REPLACE ";" ", " init_modules "${init_modules}")
 endmacro()
 
 macro(collect_module_if_found shortname)
@@ -231,7 +247,17 @@ macro(collect_module_if_found shortname)
     # If the module was found, and also the module path is the same as the
     # Qt5Core base path, we will generate the list with the modules to be installed
     set(looked_in_message ". Looked in: ${${_name_dir}}")
-    if("${${_name_found}}" AND (("${found_basepath}" GREATER "0") OR ("${found_basepath}" EQUAL "0")))
+
+    # 'found_basepath' is used to ensure consistency that all the modules are from the same Qt
+    # directory which prevents issues from arising due to mixing versions or using incompatible Qt
+    # modules. When SHIBOKEN_FORCE_PROCESS_SYSTEM_INCLUDE_PATHS is not empty, we can ignore this
+    # requirement of 'found_basepath'.
+    # This is specifically useful for Flatpak build of PySide6 where For Flatpak the modules are in
+    # different directories. For Flatpak, although the modules are in different directories, they
+    # are all compatible.
+    if("${${_name_found}}" AND
+       ((("${found_basepath}" GREATER "0") OR ("${found_basepath}" EQUAL "0")) OR
+        (NOT SHIBOKEN_FORCE_PROCESS_SYSTEM_INCLUDE_PATHS STREQUAL "")))
         message(STATUS "${module_state} module ${name} found (${ARGN})${looked_in_message}")
         # record the shortnames for the tests
         list(APPEND all_module_shortnames ${shortname})
diff --git a/sources/pyside6/doc/PySide6/QtAsyncio/coroutines.png b/sources/pyside6/doc/PySide6/QtAsyncio/coroutines.png
new file mode 100644 (file)
index 0000000..3fa00c8
Binary files /dev/null and b/sources/pyside6/doc/PySide6/QtAsyncio/coroutines.png differ
index 326f6efccb6f2c4e5591c2bd4372e4e1a27b884c..e6b2ca147d1c4e736bd54477a94cb3508b4648f2 100644 (file)
@@ -118,6 +118,9 @@ or
 to run the coroutine and then stop the event loop upon its completion.
 This latter case behaves identically to ``asyncio.run(my_coroutine())``.
 
+If there is no instance of a QCoreApplication, QGuiApplication or
+QApplication yet, a new instance of QCoreApplication is created.
+
 An additional optional argument ``quit_qapp`` can be passed to ``run()``
 to configure whether the QCoreApplication at the core of QtAsyncio
 should be shut down when asyncio finishes. A special case where one
@@ -141,10 +144,10 @@ Coroutines explained
 
 Coroutines are functions that can be paused (yield) and resumed. Behind
 this simple concept lies a complex mechanism that is abstracted by the
-asynchronous framework. This talk presents a diagram that attempts to
-illustrate the flow of a coroutine from the moment it's provided to the
-async framework until it's completed.
+asynchronous framework. `This talk <https://www.youtube.com/watch?v=XuqdTvisqkQ>`_
+presents the below diagram that attempts to illustrate the flow of a
+coroutine from the moment it's provided to the async framework until
+it's completed.
 
-.. image:: https://img.youtube.com/vi/XuqdTvisqkQ/mqdefault.jpg
-    :alt: Asynchronous programming with asyncio and Qt
-    :target: https://www.youtube.com/watch?v=XuqdTvisqkQ
+.. image:: coroutines.png
+   :alt: Coroutines explained
index e74419d6acfcdfe1c2e8b86b1813e2bca756378d..de135730e815217a699d4974e7afef1e9b7d0772 100644 (file)
@@ -109,7 +109,7 @@ simplify the installation step::
 Complementary to the wheels, you will be able to download the sources
 as well.
 
-.. note:: Wheels installed this way will be detectable by `*Qt Creator*`_, which
+.. note:: Wheels installed this way will be detectable by `Qt Creator`_, which
    will offer you to install them for your current Python interpreter.
 
 Using account.qt.io
index 8b0afa56c4fba6003d60daf7be45217d39af50a6..ceac2065a69b69231b16fd19f26fa48399d116fb 100644 (file)
@@ -117,16 +117,3 @@ for data compression. Then you can run::
 This process takes a bit longer, but in the end you have one executable ``hello.bin``::
 
     ./hello.bin
-
-
-Some Caveats
-============
-
-
-Nuitka issue on macOS
----------------------
-
-Nuitka currently has a problem with the macOS bundle files on current macOS versions.
-That has the effect that ``--standalone`` and ``--onefile`` create a crashing application.
-Older versions which don't have the recent macOS API changes from 2020 will work.
-We are currently trying to fix that problem.
index 6a218fb3a4dc8b0d1197377d6bd820d32b938c8a..8f9cb1fc669f78c8aee511dbd2450bb14a2db92c 100644 (file)
@@ -10,11 +10,7 @@ compiles your Python code to C code, and links with libpython to produce the fin
 The final executable produced has a ``.exe`` suffix on Windows, ``.bin`` on Linux and ``.app`` on
 macOS.
 
-.. note:: Although using a virtual environment for Python is recommended for ``pyside6-deploy``, do
-    not add the virtual environment to the application directory you are trying to deploy.
-    ``pyside6-deploy`` will try to package this venv folder and will eventually fail.
-
-.. note:: The default version of Nuitka used with the tool is version ``2.3.2``. This can be
+.. note:: The default version of Nuitka used with the tool is version ``2.4.8``. This can be
     updated to a newer version by updating your ``pysidedeploy.spec`` file.
 
 .. _how_pysidedeploy:
@@ -147,6 +143,12 @@ The relevant parameters for ``pyside6-deploy`` are:
 
       NSCameraUsageDescription:CameraAccess
 
+  * ``mode``: Accepts one of the options: ``onefile`` or ``standalone``. The default is ``onefile``.
+    This option corresponds to the mode in which Nuitka is run. The onefile mode creates a single
+    executable file, while the standalone mode creates a directory with the executable and all the
+    necessary files. The standalone mode is useful when you want to distribute the application as a
+    directory with dependencies and other files required by the app.
+
   * ``extra_args``: Any extra Nuitka arguments specified. It is specified as space-separated
     command line arguments i.e. just like how you would specify it when you use Nuitka through
     the command line. By default, it contains the following arguments::
@@ -218,3 +220,29 @@ are required to be installed on the system:
    * - macOS
      - dyld_info
      - Available by default from macOS 12 and upwards
+
+Creating a bug report
+=====================
+
+If you are unsure if the bug is from ``pyside6-deploy`` or ``Nuitka``:
+
+#. Create a bug report in Qt for Python. See instructions
+   `here <https://wiki.qt.io/Qt_for_Python/Reporting_Bugs/>`_.
+
+#. Run ``pyside6-deploy`` command with the ``--verbose`` option and replace ``--quiet`` with
+   ``--verbose`` in the ``extra_args`` parameter in the ``pysidedeploy.spec`` file. Attach the
+   output from stdout to the bug report.
+
+#. Attach a minimal example that reproduces the bug with the bug report.
+
+If you think the bug originates from ``Nuitka``:
+
+#. Try using a newer version of ``Nuitka``. You can change this from the ``packages`` parameter in
+   your generated ``pysidedeploy.spec`` file.
+
+#. If the bug persists, create a bug report on the
+   `Nuitka GitHub page <https://github.com/Nuitka/Nuitka/issues>`_.
+
+   * Run ``pyside6-deploy`` with the ``--dry-run`` option to see the actual ``Nuitka`` command
+     generated. Attach the ``Nuitka`` command ran to the bug report.
+   * Follow the Nuitka bug report template to create a bug report.
index 92c84259d93535de92f85546a723338229d6336b..88247a9625da49dfba711aaadf39c4e9b76d9af8 100644 (file)
@@ -19,6 +19,7 @@ Development Topics
    documentation.rst
    adapt_qt.rst
    extras.rst
+   qtasyncio.rst
 
 Implementation details
 ----------------------
diff --git a/sources/pyside6/doc/developer/qtasyncio.rst b/sources/pyside6/doc/developer/qtasyncio.rst
new file mode 100644 (file)
index 0000000..75196d8
--- /dev/null
@@ -0,0 +1,67 @@
+.. _developer-qtasyncio:
+
+QtAsyncio developer notes
+=========================
+
+QtAsyncio is a module that provides integration between Qt and Python's
+`asyncio`_ module. It allows you to run `asyncio` event loops in a Qt
+application, and to use Qt APIs from `asyncio` coroutines.
+
+.. _asyncio: https://docs.python.org/3/library/asyncio.html
+
+As a pure Python module that integrates deeply with another Python
+module, developing for QtAsyncio has some interesting aspects that
+differ from other parts of Qt for Python.
+
+QtAsyncio vs asyncio
+--------------------
+
+QtAsyncio has an interesting property from a developer perspective. As
+one of its purposes is to be a transparent replacement to asyncio's own
+event loop, for any given code that uses asyncio APIs, QtAsyncio will
+(or should!) behave identically to asyncio. (Note that the reverse does
+not apply if we also have Qt code). This is especially handy when
+debugging, as one can run the asyncio code to compare and to check the
+expected behavior. It also helps to debug both QtAsyncio and asyncio
+step by step and compare the program flow to find where QtAsyncio might
+go wrong (as far as their code differences allow). Often, one will also
+be able to implement a unit test by asserting that QtAsyncio's output is
+identical to asyncio's. (Note that a limitation to this is the fact that
+Qt's event loop does not guarantee the order of execution of callbacks,
+while asyncio does.)
+
+The step function
+-----------------
+
+When debugging, a significant amount of time will likely be spent inside
+the ``_step`` method of the ``QAsyncioTask`` class, as it is called
+often and it is where coroutines are executed. You will find that it is
+similar to asyncio's equivalent method, but with some differences. Read
+the in-line comments carefully. Some variables and associated lines of
+code might seem innocuous or even unnecessary, but provide important bug
+fixes.
+
+Keeping QtAsyncio in sync with asyncio
+--------------------------------------
+
+QtAsyncio implements `asyncio's API <https://docs.python.org/3/library/asyncio.html>`_,
+which can change with new releases. As it is part of the standard
+library, this lines up with new Python versions. It is therefore good
+practice to check whether the API has changed for every new Python
+version. This can include new classes or methods, changed signatures for
+existing functions, deprecations or outright removals. For example,
+event loop policies are expected to be deprecated with Python 3.13, with
+subsequent removal in Python 3.15. This removal will in turn require a
+small refactoring to make sure the ``QtAsyncio.run()`` function no
+longer uses the policy mechanism.
+
+asyncio developer guidance
+--------------------------
+
+asyncio's resources go beyond the pure API documentation. E.g., asyncio
+provides its own `developer guidance <https://docs.python.org/3/library/asyncio-dev.html>`_
+that is worth following when developing for QtAsyncio. In addition,
+there is documentation for
+`extending asyncio <https://docs.python.org/3/library/asyncio-extending.html>`_
+that details a few essential points to keep in mind when building your
+own event loop.
diff --git a/sources/pyside6/doc/extras/QtWebView.rst b/sources/pyside6/doc/extras/QtWebView.rst
new file mode 100644 (file)
index 0000000..1b029ac
--- /dev/null
@@ -0,0 +1,25 @@
+
+Qt WebView lets you display web content inside a QML application. To avoid including a full web
+browser stack, Qt WebView uses native APIs where appropriate.
+
+Getting Started
+^^^^^^^^^^^^^^^
+
+To include the definitions of modules classes, use the following
+directive:
+
+    ::
+
+        from PySide6.QtWebView import QtWebView
+
+To make the Qt WebView module function correctly across all platforms, it's
+necessary to call ``QtWebView.initialize()`` before creating the QGuiApplication
+instance and before window's QPlatformOpenGLContext is created. For usage,
+see the ``minibrowser`` example in the PySide6 examples package.
+
+API Reference
+^^^^^^^^^^^^^
+
+    * `Qt API <https://doc.qt.io/qt-6/qtwebview-index.html>`_
+
+The module also provides `QML types <https://doc.qt.io/qt-6/qtwebview-index.html#qml-api>`_
index 8e77a46168964543c87ee3ddb283a09c374f767a..353427208000fe40176d79d199601bf4b5cf207c 100644 (file)
@@ -197,6 +197,10 @@ Qt Modules Supported by Qt for Python
 
         Provides WebSocket communication compliant with RFC 6455.
 
+    .. grid-item-card:: :mod:`QtWebView <PySide6.QtWebView>`
+
+        Enables displaying web content in a QML application.
+
     .. grid-item-card:: :mod:`QtWidgets <PySide6.QtWidgets>`
 
         Extends Qt GUI with C++ widget functionality.
@@ -232,3 +236,4 @@ Qt Modules Supported by Qt for Python
     .. grid-item-card:: :mod:`QtAsyncio <PySide6.QtAsyncio>`
 
         Provides integration between asyncio and Qt's event loop.
+
index 7e1a210f2918686dacbaf2be9f6066d50a40bbb1..725b2335597e82910e6e13391415c4b5bbf3e755 100644 (file)
@@ -174,9 +174,10 @@ To do the same using Qt Quick:
 
 * **Declarative UI**
 
-  The UI can be described in the QML language (assigned to a Python variable)::
+  The UI can be described in the QML language:
+
+  .. code-block:: javascript
 
-    QML = """
     import QtQuick
     import QtQuick.Controls
     import QtQuick.Layouts
@@ -210,30 +211,36 @@ To do the same using Qt Quick:
             }
         }
     }
-    """
 
-  .. note:: Keep in mind ideally this content should go into
-    a ``qml`` file, but for simplicity, we are using a string variable.
+  Put the this into a file named :code:`Main.qml` into a directory named
+  :code:`Main` along with a file named :code:`qmldir` to describe a basic
+  QML module:
+
+  .. code-block:: text
+
+    module Main
+    Main 254.0 Main.qml
 
 * **Application execution**
 
   Now, add a main function where you instantiate a :ref:`QQmlApplicationEngine` and
   load the QML::
 
+    import sys
+    from PySide6.QtGui import QGuiApplication
+    from PySide6.QtQml import QQmlApplicationEngine
+
     if __name__ == "__main__":
         app = QGuiApplication(sys.argv)
         engine = QQmlApplicationEngine()
-        engine.loadData(QML.encode('utf-8'))
+        engine.addImportPath(sys.path[0])
+        engine.loadFromModule("Main", "Main")
         if not engine.rootObjects():
             sys.exit(-1)
         exit_code = app.exec()
         del engine
         sys.exit(exit_code)
 
-
-  .. note:: This is a simplified example. Normally, the QML code should be in a separate
-     :code:`.qml` file, which can be edited by design tools.
-
 .. _faq-section:
 
 Frequently Asked Questions
index 0502dd94a721bfcbbf482f82687672cc1a0cc745..6279aad2b42fbad85014954da7eaca5d4b84fb76 100644 (file)
@@ -3,16 +3,16 @@
 pyside6-qml
 ===========
 
-``pyside6-qml``  mimics some capabilities of Qt's `qml <qml_runtime>`_ runtime utility by directly
+``pyside6-qml``  mimics some capabilities of Qt's `qml`_ runtime utility by directly
 invoking QQmlEngine/QQuickView. It enables prototyping with QML/QtQuick without the need to write
-any Python code that loads the QML files either through `QQmlApplicationEngine <qqmlappengine>`_ or
-the `QQuickView <qquickview>`_ class. The tool also detects the QML classes implemented in Python
+any Python code that loads the QML files either through `QQmlApplicationEngine`_ or
+the `QQuickView`_ class. The tool also detects the QML classes implemented in Python
 and registers them with the QML type system.
 
 Usage
 -----
 
-Consider the example `Extending QML - Plugins Example <extending_qml_example>`_. This example does
+Consider the example `Extending QML - Plugins Example`_. This example does
 not have a Python file with a ``main`` function that initializes a QmlEngine to load the QML file
 ``app.qml``. You can run the example by running
 
@@ -50,8 +50,8 @@ Options
 * **--verbose/-v**: Run ``pyside6-qml`` in verbose mode. When run in this mode, pyside6-qml prints
   log messages during various stages of processing.
 
-Options that align with `QML <qml_runtime>`_ runtime utility
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Options that align with the `qml`_ runtime utility
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 * **--app-typ/-a**: Specifies which application class to use. It takes one of the three values -
   ``core, gui, widget``. The default value is *gui*.
@@ -78,7 +78,7 @@ Options that align with `QML <qml_runtime>`_ runtime utility
 
 * **--disable-context-sharing**: Disable the use of a shared GL context for QtQuick Windows".
 
-.. _`qml_runtime`: https://doc.qt.io/qt-6/qtquick-qml-runtime.html
-.. _`qqmlappengine`: https://doc.qt.io/qt-6/qqmlapplicationengine.html
-.. _`qquickview`: https://doc.qt.io/qt-6/qquickview.html
-.. _`extending_qml_example`: https://doc.qt.io/qtforpython-6/examples/example_qml_tutorials_extending-qml_chapter6-plugins.html
+.. _`qml`: https://doc.qt.io/qt-6/qtquick-qml-runtime.html
+.. _`QQmlApplicationEngine`: https://doc.qt.io/qt-6/qqmlapplicationengine.html
+.. _`QQuickView`: https://doc.qt.io/qt-6/qquickview.html
+.. _`Extending QML - Plugins Example`: https://doc.qt.io/qtforpython-6/examples/example_qml_tutorials_extending-qml_chapter6-plugins.html
index 49cd3d94ac93055cc3d4bbb7535140287114bb24..ba0a08fef0d29d63cc0d2304ac721ce6733fb850 100644 (file)
@@ -8,12 +8,11 @@ user interface is specified as a tree of objects with properties. In
 this tutorial, we will show how to make a simple "Hello World"
 application with PySide6 and QML.
 
-A PySide6/QML application consists, at least, of two different files -
+A PySide6/QML application consists, mainly, of two different files -
 a file with the QML description of the user interface, and a python file
-that loads the QML file. To make things easier, let's save both files in
-the same directory.
+that loads the QML file.
 
-Here is a simple QML file called :code:`view.qml`:
+Here is a simple QML file called :code:`Main.qml`:
 
 .. code-block:: javascript
 
@@ -40,27 +39,38 @@ that reads "Hello World". The code :code:`anchors.centerIn: main` makes
 the text appear centered within the object with :code:`id: main`,
 which is the Rectangle in this case.
 
+Put the file into into a directory named :code:`Main` along
+with a file named :code:`qmldir` to describe a basic QML module:
+
+.. code-block:: text
+
+    module Main
+    Main 254.0 Main.qml
+
 Now, let's see how the code looks on the PySide6.
 Let's call it :code:`main.py`:
 
 .. code-block:: python
 
     import sys
-    from PySide6.QtWidgets import QApplication
+    from PySide6.QtGui import QGuiApplication
     from PySide6.QtQuick import QQuickView
 
     if __name__ == "__main__":
-        app = QApplication()
+        app = QGuiApplication()
         view = QQuickView()
-
-        view.setSource("view.qml")
+        view.engine().addImportPath(sys.path[0])
+        view.loadFromModule("Main", "Main")
         view.show()
-        sys.exit(app.exec())
+        ex = app.exec()
+        del view
+        sys.exit(ex)
 
 If you are already familiar with PySide6 and have followed our
 tutorials, you have already seen much of this code.
-The only novelties are that you must :code:`import QtQuick` and set the
-source of the :code:`QQuickView` object to the URL of your QML file.
+The only novelties are that you must :code:`import QtQuick`,
+add the directory to the import paths, and instruct the
+:code:`QQuickView` to load our module.
 Then, similar to what you do with any Qt widget, you call
 :code:`QQuickView.show()`.
 
index 858293beb6e9ab246e77817bdaf2ded6ad62442d..7554ae10d049f30835cb2d8f78b6f3275d7e7ecf 100644 (file)
@@ -78,87 +78,88 @@ To use the generated file, add the following import at the top of your main Pyth
 Changes in the code
 ===================
 
-As you are modifying an existing example, you need to modify the following
-lines:
+As you are modifying an existing example, you need to modify the
+``player.py`` file. At the top, change
 
 .. code-block:: python
 
     from PySide6.QtGui import QIcon, QKeySequence
-    playIcon = self.style().standardIcon(QStyle.SP_MediaPlay)
-    previousIcon = self.style().standardIcon(QStyle.SP_MediaSkipBackward)
-    pauseIcon = self.style().standardIcon(QStyle.SP_MediaPause)
-    nextIcon = self.style().standardIcon(QStyle.SP_MediaSkipForward)
-    stopIcon = self.style().standardIcon(QStyle.SP_MediaStop)
 
-and replace them with the following:
+to:
 
 .. code-block:: python
 
     from PySide6.QtGui import QIcon, QKeySequence, QPixmap
-    playIcon = QIcon(QPixmap(":/icons/play.png"))
-    previousIcon = QIcon(QPixmap(":/icons/previous.png"))
-    pauseIcon = QIcon(QPixmap(":/icons/pause.png"))
-    nextIcon = QIcon(QPixmap(":/icons/forward.png"))
-    stopIcon = QIcon(QPixmap(":/icons/stop.png"))
-
-This ensures that the new icons are used instead of the default ones provided
-by the application theme.
-Notice that the lines are not consecutive, but are in different parts
-of the file.
 
-After all your imports, add the following
+Below the imports, add the following:
 
 .. code-block:: python
 
     import rc_icons
 
-Now, the constructor of your class should look like this:
+In the ``MainWindow.__init__()`` function, replace the code
+loading the icons from the theme by your custom icons:
 
 .. code-block:: python
 
-    def __init__(self):
-        super(MainWindow, self).__init__()
-
-        self.playlist = QMediaPlaylist()
-        self.player = QMediaPlayer()
+    playIcon = QIcon(QPixmap(":/icons/play.png"))
+    self._play_action = tool_bar.addAction(playIcon, "Play")
 
-        toolBar = QToolBar()
-        self.addToolBar(toolBar)
+When doing this for all icons, the constructor of your class should
+look like this:
 
-        fileMenu = self.menuBar().addMenu("&File")
-        openAction = QAction(QIcon.fromTheme("document-open"),
-                             "&Open...", self, shortcut=QKeySequence.Open,
-                             triggered=self.open)
-        fileMenu.addAction(openAction)
-        exitAction = QAction(QIcon.fromTheme("application-exit"), "E&xit",
-                             self, shortcut="Ctrl+Q", triggered=self.close)
-        fileMenu.addAction(exitAction)
+.. code-block:: python
 
-        playMenu = self.menuBar().addMenu("&Play")
+    def __init__(self):
+        super().__init__()
+
+        self._playlist = []
+        self._playlist_index = -1
+        self._audio_output = QAudioOutput()
+        self._player = QMediaPlayer()
+        self._player.setAudioOutput(self._audio_output)
+
+        self._player.errorOccurred.connect(self._player_error)
+
+        tool_bar = QToolBar()
+        self.addToolBar(tool_bar)
+
+        file_menu = self.menuBar().addMenu("&File")
+        icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen)
+        open_action = QAction(icon, "&Open...", self,
+                              shortcut=QKeySequence.Open, triggered=self.open)
+        file_menu.addAction(open_action)
+        tool_bar.addAction(open_action)
+        icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit)
+        exit_action = QAction(icon, "E&xit", self,
+                              shortcut="Ctrl+Q", triggered=self.close)
+        file_menu.addAction(exit_action)
+
+        play_menu = self.menuBar().addMenu("&Play")
         playIcon = QIcon(QPixmap(":/icons/play.png"))
-        self.playAction = toolBar.addAction(playIcon, "Play")
-        self.playAction.triggered.connect(self.player.play)
-        playMenu.addAction(self.playAction)
+        self._play_action = tool_bar.addAction(playIcon, "Play")
+        self._play_action.triggered.connect(self._player.play)
+        play_menu.addAction(self._play_action)
 
         previousIcon = QIcon(QPixmap(":/icons/previous.png"))
-        self.previousAction = toolBar.addAction(previousIcon, "Previous")
-        self.previousAction.triggered.connect(self.previousClicked)
-        playMenu.addAction(self.previousAction)
+        self._previous_action = tool_bar.addAction(previousIcon, "Previous")
+        self._previous_action.triggered.connect(self.previous_clicked)
+        play_menu.addAction(self._previous_action)
 
         pauseIcon = QIcon(QPixmap(":/icons/pause.png"))
-        self.pauseAction = toolBar.addAction(pauseIcon, "Pause")
-        self.pauseAction.triggered.connect(self.player.pause)
-        playMenu.addAction(self.pauseAction)
+        self._pause_action = tool_bar.addAction(pauseIcon, "Pause")
+        self._pause_action.triggered.connect(self._player.pause)
+        play_menu.addAction(self._pause_action)
 
         nextIcon = QIcon(QPixmap(":/icons/forward.png"))
-        self.nextAction = toolBar.addAction(nextIcon, "Next")
-        self.nextAction.triggered.connect(self.playlist.next)
-        playMenu.addAction(self.nextAction)
+        self._next_action = tool_bar.addAction(nextIcon, "Next")
+        self._next_action.triggered.connect(self.next_clicked)
+        play_menu.addAction(self._next_action)
 
         stopIcon = QIcon(QPixmap(":/icons/stop.png"))
-        self.stopAction = toolBar.addAction(stopIcon, "Stop")
-        self.stopAction.triggered.connect(self.player.stop)
-        playMenu.addAction(self.stopAction)
+        self._stop_action = tool_bar.addAction(stopIcon, "Stop")
+        self._stop_action.triggered.connect(self._ensure_stopped)
+        play_menu.addAction(self._stop_action)
 
         # many lines were omitted
 
diff --git a/sources/pyside6/doc/tutorials/qmlapp/Main/Main.qml b/sources/pyside6/doc/tutorials/qmlapp/Main/Main.qml
new file mode 100644 (file)
index 0000000..7f9b1d7
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+Page {
+    width: 640
+    height: 480
+    required property var myModel
+
+    header: Label {
+        color: "#15af15"
+        text: qsTr("Where do people use Qt?")
+        font.pointSize: 17
+        font.bold: true
+        font.family: "Arial"
+        renderType: Text.NativeRendering
+        horizontalAlignment: Text.AlignHCenter
+        padding: 10
+    }
+    Rectangle {
+        id: root
+        width: parent.width
+        height: parent.height
+
+        Image {
+            id: image
+            fillMode: Image.PreserveAspectFit
+            anchors.centerIn: root
+            source: "./logo.png"
+            opacity: 0.5
+
+        }
+
+        ListView {
+            id: view
+            anchors.fill: root
+            anchors.margins: 25
+            model: myModel
+            delegate: Text {
+                anchors.leftMargin: 50
+                font.pointSize: 15
+                horizontalAlignment: Text.AlignHCenter
+                text: display
+            }
+        }
+    }
+    NumberAnimation {
+        id: anim
+        running: true
+        target: view
+        property: "contentY"
+        duration: 500
+    }
+}
diff --git a/sources/pyside6/doc/tutorials/qmlapp/Main/logo.png b/sources/pyside6/doc/tutorials/qmlapp/Main/logo.png
new file mode 100644 (file)
index 0000000..30c621c
Binary files /dev/null and b/sources/pyside6/doc/tutorials/qmlapp/Main/logo.png differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/Main/qmldir b/sources/pyside6/doc/tutorials/qmlapp/Main/qmldir
new file mode 100644 (file)
index 0000000..8ad738e
--- /dev/null
@@ -0,0 +1,2 @@
+module Main
+Main 254.0 Main.qml
diff --git a/sources/pyside6/doc/tutorials/qmlapp/logo.png b/sources/pyside6/doc/tutorials/qmlapp/logo.png
deleted file mode 100644 (file)
index 30c621c..0000000
Binary files a/sources/pyside6/doc/tutorials/qmlapp/logo.png and /dev/null differ
index 8b1b254402263ececbe2fccb57227132b65db4f8..3ab440531a895e2d260602424fc28fab47c0eb44 100644 (file)
@@ -1,46 +1,47 @@
 # Copyright (C) 2022 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
 
 import sys
 import urllib.request
 import json
-from pathlib import Path
 
 from PySide6.QtQuick import QQuickView
-from PySide6.QtCore import QStringListModel, QUrl
+from PySide6.QtCore import QStringListModel
 from PySide6.QtGui import QGuiApplication
 
 
 if __name__ == '__main__':
 
-    #get our data
+    # get our data
     url = "http://country.io/names.json"
     response = urllib.request.urlopen(url)
     data = json.loads(response.read().decode('utf-8'))
 
-    #Format and sort the data
+    # Format and sort the data
     data_list = list(data.values())
     data_list.sort()
 
-    #Set up the application window
+    # Set up the application window
     app = QGuiApplication(sys.argv)
     view = QQuickView()
     view.setResizeMode(QQuickView.SizeRootObjectToView)
 
-    #Expose the list to the Qml code
+    # Expose the list to the Qml code
     my_model = QStringListModel()
     my_model.setStringList(data_list)
     view.setInitialProperties({"myModel": my_model})
 
-    #Load the QML file
-    qml_file = Path(__file__).parent / "view.qml"
-    view.setSource(QUrl.fromLocalFile(qml_file.resolve()))
+    # Load the QML file
+    # Add the current directory to the import paths and load the main module.
+    view.engine().addImportPath(sys.path[0])
+    view.loadFromModule("Main", "Main")
 
-    #Show the window
+    # Show the window
     if view.status() == QQuickView.Error:
         sys.exit(-1)
     view.show()
 
-    #execute and cleanup
+    # execute and cleanup
     app.exec()
     del view
index c6d72e74202e34b3049de03ff8d8972601d4fa02..5b7e7d4e0da2fad70799c90dcb4d1ea7d986e1b6 100644 (file)
@@ -42,8 +42,9 @@ development process using *Qt Creator*:
    This should create a ``main.py`` and ```main.pyproject`` files
    for the project.
 
-#. Download :download:`view.qml<view.qml>` and :download:`logo.png <logo.png>`
-   and move them to your project folder.
+#. Download :download:`Main.qml<Main/Main.qml>`, :download:`qmldir<Main/qmldir>`
+   and :download:`logo.png <Main/logo.png>` and place them in a subdirectory
+   named `Main` in your project folder. This creates a basic QML module.
 
 #. Double-click on ``main.pyproject`` to open it in edit mode, and append
    ``view.qml`` and ``logo.png`` to the **files** list. This is how your
@@ -52,7 +53,7 @@ development process using *Qt Creator*:
    .. code::
 
     {
-        "files": ["main.py", "view.qml", "logo.png"]
+        "files": ["main.py", "Main/Main.qml", "Main/logo.png", "Main/qmldir"]
     }
 
 #. Now that you have the necessary bits for the application, import the
@@ -61,8 +62,8 @@ development process using *Qt Creator*:
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 3-23
-      :emphasize-lines: 7-9,14-17
+      :lines: 5-23
+      :emphasize-lines: 5-7,12-15
 
 #. Now, set up the application window using
    :ref:`PySide6.QtGui.QGuiApplication<qguiapplication>`, which manages the application-wide
@@ -70,8 +71,8 @@ development process using *Qt Creator*:
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 3-28
-      :emphasize-lines: 23-25
+      :lines: 5-28
+      :emphasize-lines: 21-24
 
    .. note:: Setting the resize policy is important if you want the
       root item to resize itself to fit the window or vice-a-versa.
@@ -83,23 +84,23 @@ development process using *Qt Creator*:
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 3-33
-      :emphasize-lines: 28-31
+      :lines: 5-33
+      :emphasize-lines: 26-29
 
-#. Load the ``view.qml`` to the ``QQuickView`` and call ``show()`` to
+#. Load the ``Main.qml`` to the ``QQuickView`` and call ``show()`` to
    display the application window.
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 3-42
-      :emphasize-lines: 33-40
+      :lines: 5-43
+      :emphasize-lines: 31-39
 
 #. Finally, execute the application to start the event loop and clean up.
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 3-
-      :emphasize-lines: 42-44
+      :lines: 5-
+      :emphasize-lines: 41-43
 
 #. Your application is ready to be run now. Select **Projects** mode to
    choose the Python version to run it.
@@ -128,5 +129,5 @@ this application:
 Related information
 ********************
 
-* `QML Reference <https://doc.qt.io/qt-5/qmlreference.html>`_
+* `QML Reference <https://doc.qt.io/qt-6/qmlreference.html>`_
 * :doc:`../qmlintegration/qmlintegration`
diff --git a/sources/pyside6/doc/tutorials/qmlapp/view.qml b/sources/pyside6/doc/tutorials/qmlapp/view.qml
deleted file mode 100644 (file)
index 7f9b1d7..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick 2.12
-import QtQuick.Controls 2.12
-
-Page {
-    width: 640
-    height: 480
-    required property var myModel
-
-    header: Label {
-        color: "#15af15"
-        text: qsTr("Where do people use Qt?")
-        font.pointSize: 17
-        font.bold: true
-        font.family: "Arial"
-        renderType: Text.NativeRendering
-        horizontalAlignment: Text.AlignHCenter
-        padding: 10
-    }
-    Rectangle {
-        id: root
-        width: parent.width
-        height: parent.height
-
-        Image {
-            id: image
-            fillMode: Image.PreserveAspectFit
-            anchors.centerIn: root
-            source: "./logo.png"
-            opacity: 0.5
-
-        }
-
-        ListView {
-            id: view
-            anchors.fill: root
-            anchors.margins: 25
-            model: myModel
-            delegate: Text {
-                anchors.leftMargin: 50
-                font.pointSize: 15
-                horizontalAlignment: Text.AlignHCenter
-                text: display
-            }
-        }
-    }
-    NumberAnimation {
-        id: anim
-        running: true
-        target: view
-        property: "contentY"
-        duration: 500
-    }
-}
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/Main/Main.qml b/sources/pyside6/doc/tutorials/qmlintegration/Main/Main.qml
new file mode 100644 (file)
index 0000000..635603f
--- /dev/null
@@ -0,0 +1,160 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+import QtQuick 2.0
+import QtQuick.Layouts 1.11
+import QtQuick.Controls 2.1
+import QtQuick.Window 2.1
+import QtQuick.Controls.Material 2.1
+
+import io.qt.textproperties 1.0
+
+ApplicationWindow {
+    id: page
+    width: 800
+    height: 400
+    visible: true
+    Material.theme: Material.Dark
+    Material.accent: Material.Red
+
+    Bridge {
+        id: bridge
+    }
+
+    GridLayout {
+        id: grid
+        columns: 2
+        rows: 3
+
+        ColumnLayout {
+            spacing: 2
+            Layout.columnSpan: 1
+            Layout.preferredWidth: 400
+
+            Text {
+                id: leftlabel
+                Layout.alignment: Qt.AlignHCenter
+                color: "white"
+                font.pointSize: 16
+                text: "Qt for Python"
+                Layout.preferredHeight: 100
+                Material.accent: Material.Green
+            }
+
+            RadioButton {
+                id: italic
+                Layout.alignment: Qt.AlignLeft
+                text: "Italic"
+                onToggled: {
+                    leftlabel.font.italic = bridge.getItalic(italic.text)
+                    leftlabel.font.bold = bridge.getBold(italic.text)
+                    leftlabel.font.underline = bridge.getUnderline(italic.text)
+
+                }
+            }
+            RadioButton {
+                id: bold
+                Layout.alignment: Qt.AlignLeft
+                text: "Bold"
+                onToggled: {
+                    leftlabel.font.italic = bridge.getItalic(bold.text)
+                    leftlabel.font.bold = bridge.getBold(bold.text)
+                    leftlabel.font.underline = bridge.getUnderline(bold.text)
+                }
+            }
+            RadioButton {
+                id: underline
+                Layout.alignment: Qt.AlignLeft
+                text: "Underline"
+                onToggled: {
+                    leftlabel.font.italic = bridge.getItalic(underline.text)
+                    leftlabel.font.bold = bridge.getBold(underline.text)
+                    leftlabel.font.underline = bridge.getUnderline(underline.text)
+                }
+            }
+            RadioButton {
+                id: noneradio
+                Layout.alignment: Qt.AlignLeft
+                text: "None"
+                checked: true
+                onToggled: {
+                    leftlabel.font.italic = bridge.getItalic(noneradio.text)
+                    leftlabel.font.bold = bridge.getBold(noneradio.text)
+                    leftlabel.font.underline = bridge.getUnderline(noneradio.text)
+                }
+            }
+        }
+
+        ColumnLayout {
+            id: rightcolumn
+            spacing: 2
+            Layout.columnSpan: 1
+            Layout.preferredWidth: 400
+            Layout.preferredHeight: 400
+            Layout.fillWidth: true
+
+            RowLayout {
+                Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+
+
+                Button {
+                    id: red
+                    text: "Red"
+                    highlighted: true
+                    Material.accent: Material.Red
+                    onClicked: {
+                        leftlabel.color = bridge.getColor(red.text)
+                    }
+                }
+                Button {
+                    id: green
+                    text: "Green"
+                    highlighted: true
+                    Material.accent: Material.Green
+                    onClicked: {
+                        leftlabel.color = bridge.getColor(green.text)
+                    }
+                }
+                Button {
+                    id: blue
+                    text: "Blue"
+                    highlighted: true
+                    Material.accent: Material.Blue
+                    onClicked: {
+                        leftlabel.color = bridge.getColor(blue.text)
+                    }
+                }
+                Button {
+                    id: nonebutton
+                    text: "None"
+                    highlighted: true
+                    Material.accent: Material.BlueGrey
+                    onClicked: {
+                        leftlabel.color = bridge.getColor(nonebutton.text)
+                    }
+                }
+            }
+            RowLayout {
+                Layout.fillWidth: true
+                Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+                Text {
+                    id: rightlabel
+                    color: "white"
+                    Layout.alignment: Qt.AlignLeft
+                    text: "Font size"
+                    Material.accent: Material.White
+                }
+                Slider {
+                    width: rightcolumn.width*0.6
+                    Layout.alignment: Qt.AlignRight
+                    id: slider
+                    value: 0.5
+                    onValueChanged: {
+                        leftlabel.font.pointSize = bridge.getSize(value)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/Main/qmldir b/sources/pyside6/doc/tutorials/qmlintegration/Main/qmldir
new file mode 100644 (file)
index 0000000..8ad738e
--- /dev/null
@@ -0,0 +1,2 @@
+module Main
+Main 254.0 Main.qml
index 0a751d7d1b98ecaa08d0846f77b221b0440731dd..574e314de18da325eb8d82781334c25660029f75 100644 (file)
@@ -1,15 +1,15 @@
 # Copyright (C) 2022 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
 
 import sys
-from pathlib import Path
 
 from PySide6.QtCore import QObject, Slot
 from PySide6.QtGui import QGuiApplication
 from PySide6.QtQml import QQmlApplicationEngine, QmlElement
 from PySide6.QtQuickControls2 import QQuickStyle
 
-import style_rc
+import rc_style  # noqa F401
 
 # To be used on the @QmlElement decorator
 # (QML_IMPORT_MINOR_VERSION is optional)
@@ -24,54 +24,41 @@ class Bridge(QObject):
     def getColor(self, s):
         if s.lower() == "red":
             return "#ef9a9a"
-        elif s.lower() == "green":
+        if s.lower() == "green":
             return "#a5d6a7"
-        elif s.lower() == "blue":
+        if s.lower() == "blue":
             return "#90caf9"
-        else:
-            return "white"
+        return "white"
 
     @Slot(float, result=int)
     def getSize(self, s):
         size = int(s * 34)
-        if size <= 0:
-            return 1
-        else:
-            return size
+        return max(1, size)
 
     @Slot(str, result=bool)
     def getItalic(self, s):
-        if s.lower() == "italic":
-            return True
-        else:
-            return False
+        return s.lower() == "italic"
 
     @Slot(str, result=bool)
     def getBold(self, s):
-        if s.lower() == "bold":
-            return True
-        else:
-            return False
+        return s.lower() == "bold"
 
     @Slot(str, result=bool)
     def getUnderline(self, s):
-        if s.lower() == "underline":
-            return True
-        else:
-            return False
+        return s.lower() == "underline"
 
 
 if __name__ == '__main__':
     app = QGuiApplication(sys.argv)
     QQuickStyle.setStyle("Material")
     engine = QQmlApplicationEngine()
-
-    # Get the path of the current directory, and then add the name
-    # of the QML file, to load it.
-    qml_file = Path(__file__).parent / 'view.qml'
-    engine.load(qml_file)
+    # Add the current directory to the import paths and load the main module.
+    engine.addImportPath(sys.path[0])
+    engine.loadFromModule("Main", "Main")
 
     if not engine.rootObjects():
         sys.exit(-1)
 
-    sys.exit(app.exec())
+    ex = app.exec()
+    del engine
+    sys.exit(ex)
index ff6fe3e31f3c9b73fcca26bd3f4495a40c184cc1..3d127529ce907e081c70f791e6a797192550f4df 100644 (file)
@@ -29,8 +29,8 @@ application and PySide6 integration:
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 63-76
-      :emphasize-lines: 4,9
+      :lines: 51-64
+      :emphasize-lines: 6,7
 
    Notice that we only need a :code:`QQmlApplicationEngine` to
    :code:`load` the QML file.
@@ -40,7 +40,7 @@ application and PySide6 integration:
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 14-54
+      :lines: 14-49
       :emphasize-lines: 3,4,7
 
    Notice that the registration happens thanks to the :code:`QmlElement`
@@ -61,7 +61,7 @@ application and PySide6 integration:
    This :code:`id` will help you to get a reference to the element
    that was registered from Python.
 
-   .. literalinclude:: view.qml
+   .. literalinclude:: Main/Main.qml
       :linenos:
       :lines: 45-55
       :emphasize-lines: 6-8
@@ -75,13 +75,20 @@ application and PySide6 integration:
    will return *False*, that is how we make sure only one is being
    applied to the text.
 
+#. Put the file into into a directory named :code:`Main` along
+   with a file named :code:`qmldir` to describe a basic QML module:
+
+   .. code-block:: text
+
+       module Main
+       Main 254.0 Main.qml
+
 #. Each slot verifies if the selected option contains the text associated
    to the property:
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 42-47
-      :emphasize-lines: 4,6
+      :lines: 42-44
 
    Returning *True* or *False* allows you to activate and deactivate
    the properties of the QML UI elements.
@@ -91,7 +98,7 @@ application and PySide6 integration:
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 34-39
+      :lines: 33-36
 
 #. Now, for changing the look of our application, you have two options:
 
@@ -109,13 +116,13 @@ application and PySide6 integration:
       .. literalinclude:: style.qrc
          :linenos:
 
-      Generate the *rc* file running, ``pyside6-rcc style.qrc -o style_rc.py``
+      Generate the *rc* file running, ``pyside6-rcc style.qrc -o rc_style.py``
       And finally import it from your ``main.py`` script.
 
    .. literalinclude:: main.py
       :linenos:
-      :lines: 4-12
-      :emphasize-lines: 9
+      :lines: 5-12
+      :emphasize-lines: 8
 
    You can read more about this configuration file
    `here <https://doc.qt.io/qt-5/qtquickcontrols2-configuration.html>`_.
@@ -124,5 +131,6 @@ application and PySide6 integration:
 
    .. image:: textproperties_material.png
 
-You can :download:`view.qml <view.qml>` and
-:download:`main.py <main.py>` to try this example.
+You can download :download:`Main.qml <Main/Main.qml>`,
+:download:`qmldir <Main/qmldir>` and :download:`main.py <main.py>`
+to try this example.
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/view.qml b/sources/pyside6/doc/tutorials/qmlintegration/view.qml
deleted file mode 100644 (file)
index 635603f..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-
-import QtQuick 2.0
-import QtQuick.Layouts 1.11
-import QtQuick.Controls 2.1
-import QtQuick.Window 2.1
-import QtQuick.Controls.Material 2.1
-
-import io.qt.textproperties 1.0
-
-ApplicationWindow {
-    id: page
-    width: 800
-    height: 400
-    visible: true
-    Material.theme: Material.Dark
-    Material.accent: Material.Red
-
-    Bridge {
-        id: bridge
-    }
-
-    GridLayout {
-        id: grid
-        columns: 2
-        rows: 3
-
-        ColumnLayout {
-            spacing: 2
-            Layout.columnSpan: 1
-            Layout.preferredWidth: 400
-
-            Text {
-                id: leftlabel
-                Layout.alignment: Qt.AlignHCenter
-                color: "white"
-                font.pointSize: 16
-                text: "Qt for Python"
-                Layout.preferredHeight: 100
-                Material.accent: Material.Green
-            }
-
-            RadioButton {
-                id: italic
-                Layout.alignment: Qt.AlignLeft
-                text: "Italic"
-                onToggled: {
-                    leftlabel.font.italic = bridge.getItalic(italic.text)
-                    leftlabel.font.bold = bridge.getBold(italic.text)
-                    leftlabel.font.underline = bridge.getUnderline(italic.text)
-
-                }
-            }
-            RadioButton {
-                id: bold
-                Layout.alignment: Qt.AlignLeft
-                text: "Bold"
-                onToggled: {
-                    leftlabel.font.italic = bridge.getItalic(bold.text)
-                    leftlabel.font.bold = bridge.getBold(bold.text)
-                    leftlabel.font.underline = bridge.getUnderline(bold.text)
-                }
-            }
-            RadioButton {
-                id: underline
-                Layout.alignment: Qt.AlignLeft
-                text: "Underline"
-                onToggled: {
-                    leftlabel.font.italic = bridge.getItalic(underline.text)
-                    leftlabel.font.bold = bridge.getBold(underline.text)
-                    leftlabel.font.underline = bridge.getUnderline(underline.text)
-                }
-            }
-            RadioButton {
-                id: noneradio
-                Layout.alignment: Qt.AlignLeft
-                text: "None"
-                checked: true
-                onToggled: {
-                    leftlabel.font.italic = bridge.getItalic(noneradio.text)
-                    leftlabel.font.bold = bridge.getBold(noneradio.text)
-                    leftlabel.font.underline = bridge.getUnderline(noneradio.text)
-                }
-            }
-        }
-
-        ColumnLayout {
-            id: rightcolumn
-            spacing: 2
-            Layout.columnSpan: 1
-            Layout.preferredWidth: 400
-            Layout.preferredHeight: 400
-            Layout.fillWidth: true
-
-            RowLayout {
-                Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
-
-
-                Button {
-                    id: red
-                    text: "Red"
-                    highlighted: true
-                    Material.accent: Material.Red
-                    onClicked: {
-                        leftlabel.color = bridge.getColor(red.text)
-                    }
-                }
-                Button {
-                    id: green
-                    text: "Green"
-                    highlighted: true
-                    Material.accent: Material.Green
-                    onClicked: {
-                        leftlabel.color = bridge.getColor(green.text)
-                    }
-                }
-                Button {
-                    id: blue
-                    text: "Blue"
-                    highlighted: true
-                    Material.accent: Material.Blue
-                    onClicked: {
-                        leftlabel.color = bridge.getColor(blue.text)
-                    }
-                }
-                Button {
-                    id: nonebutton
-                    text: "None"
-                    highlighted: true
-                    Material.accent: Material.BlueGrey
-                    onClicked: {
-                        leftlabel.color = bridge.getColor(nonebutton.text)
-                    }
-                }
-            }
-            RowLayout {
-                Layout.fillWidth: true
-                Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
-                Text {
-                    id: rightlabel
-                    color: "white"
-                    Layout.alignment: Qt.AlignLeft
-                    text: "Font size"
-                    Material.accent: Material.White
-                }
-                Slider {
-                    width: rightcolumn.width*0.6
-                    Layout.alignment: Qt.AlignRight
-                    id: slider
-                    value: 0.5
-                    onValueChanged: {
-                        leftlabel.font.pointSize = bridge.getSize(value)
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/Main/Main.qml b/sources/pyside6/doc/tutorials/qmlsqlintegration/Main/Main.qml
new file mode 100644 (file)
index 0000000..889385f
--- /dev/null
@@ -0,0 +1,98 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import ChatModel
+
+ApplicationWindow {
+    id: window
+    title: qsTr("Chat")
+    width: 640
+    height: 960
+    visible: true
+
+    SqlConversationModel {
+        id: chat_model
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+
+        ListView {
+            id: listView
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Layout.margins: pane.leftPadding + messageField.leftPadding
+            displayMarginBeginning: 40
+            displayMarginEnd: 40
+            verticalLayoutDirection: ListView.BottomToTop
+            spacing: 12
+            model: chat_model
+            delegate: Column {
+                anchors.right: sentByMe ? listView.contentItem.right : undefined
+                spacing: 6
+
+                readonly property bool sentByMe: model.recipient !== "Me"
+                Row {
+                    id: messageRow
+                    spacing: 6
+                    anchors.right: sentByMe ? parent.right : undefined
+
+                    Rectangle {
+                        width: Math.min(messageText.implicitWidth + 24,
+                            listView.width - (!sentByMe ? messageRow.spacing : 0))
+                        height: messageText.implicitHeight + 24
+                        radius: 15
+                        color: sentByMe ? "lightgrey" : "steelblue"
+
+                        Label {
+                            id: messageText
+                            text: model.message
+                            color: sentByMe ? "black" : "white"
+                            anchors.fill: parent
+                            anchors.margins: 12
+                            wrapMode: Label.Wrap
+                        }
+                    }
+                }
+
+                Label {
+                    id: timestampText
+                    text: Qt.formatDateTime(model.timestamp, "d MMM hh:mm")
+                    color: "lightgrey"
+                    anchors.right: sentByMe ? parent.right : undefined
+                }
+            }
+
+            ScrollBar.vertical: ScrollBar {}
+        }
+
+        Pane {
+            id: pane
+            Layout.fillWidth: true
+
+            RowLayout {
+                width: parent.width
+
+                TextArea {
+                    id: messageField
+                    Layout.fillWidth: true
+                    placeholderText: qsTr("Compose message")
+                    wrapMode: TextArea.Wrap
+                }
+
+                Button {
+                    id: sendButton
+                    text: qsTr("Send")
+                    enabled: messageField.length > 0
+                    onClicked: {
+                        listView.model.send_message("machine", messageField.text, "Me");
+                        messageField.text = "";
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/Main/qmldir b/sources/pyside6/doc/tutorials/qmlsqlintegration/Main/qmldir
new file mode 100644 (file)
index 0000000..8ad738e
--- /dev/null
@@ -0,0 +1,2 @@
+module Main
+Main 254.0 Main.qml
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml b/sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml
deleted file mode 100644 (file)
index da58ae9..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import ChatModel
-
-ApplicationWindow {
-    id: window
-    title: qsTr("Chat")
-    width: 640
-    height: 960
-    visible: true
-
-    SqlConversationModel {
-        id: chat_model
-    }
-
-    ColumnLayout {
-        anchors.fill: window
-
-        ListView {
-            id: listView
-            Layout.fillWidth: true
-            Layout.fillHeight: true
-            Layout.margins: pane.leftPadding + messageField.leftPadding
-            displayMarginBeginning: 40
-            displayMarginEnd: 40
-            verticalLayoutDirection: ListView.BottomToTop
-            spacing: 12
-            model: chat_model
-            delegate: Column {
-                anchors.right: sentByMe ? listView.contentItem.right : undefined
-                spacing: 6
-
-                readonly property bool sentByMe: model.recipient !== "Me"
-                Row {
-                    id: messageRow
-                    spacing: 6
-                    anchors.right: sentByMe ? parent.right : undefined
-
-                    Rectangle {
-                        width: Math.min(messageText.implicitWidth + 24,
-                            listView.width - (!sentByMe ? messageRow.spacing : 0))
-                        height: messageText.implicitHeight + 24
-                        radius: 15
-                        color: sentByMe ? "lightgrey" : "steelblue"
-
-                        Label {
-                            id: messageText
-                            text: model.message
-                            color: sentByMe ? "black" : "white"
-                            anchors.fill: parent
-                            anchors.margins: 12
-                            wrapMode: Label.Wrap
-                        }
-                    }
-                }
-
-                Label {
-                    id: timestampText
-                    text: Qt.formatDateTime(model.timestamp, "d MMM hh:mm")
-                    color: "lightgrey"
-                    anchors.right: sentByMe ? parent.right : undefined
-                }
-            }
-
-            ScrollBar.vertical: ScrollBar {}
-        }
-
-        Pane {
-            id: pane
-            Layout.fillWidth: true
-
-            RowLayout {
-                width: parent.width
-
-                TextArea {
-                    id: messageField
-                    Layout.fillWidth: true
-                    placeholderText: qsTr("Compose message")
-                    wrapMode: TextArea.Wrap
-                }
-
-                Button {
-                    id: sendButton
-                    text: qsTr("Send")
-                    enabled: messageField.length > 0
-                    onClicked: {
-                        listView.model.send_message("machine", messageField.text, "Me");
-                        messageField.text = "";
-                    }
-                }
-            }
-        }
-    }
-}
index 314fd5aa5ca4c1e9c30b1b7ee380dc4f642e1bbc..92fcf7c874a9fa2b6f6c3e7ee02b748257fb0a13 100644 (file)
@@ -1,16 +1,17 @@
 # Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
 
 import sys
 import logging
 
-from PySide6.QtCore import QDir, QFile, QUrl
+from PySide6.QtCore import QCoreApplication, QDir, QFile, QStandardPaths
 from PySide6.QtGui import QGuiApplication
 from PySide6.QtQml import QQmlApplicationEngine
 from PySide6.QtSql import QSqlDatabase
 
 # We import the file just to trigger the QmlElement type registration.
-import sqlDialog
+import sqlDialog  # noqa E703
 
 logging.basicConfig(filename="chat.log", level=logging.DEBUG)
 logger = logging.getLogger("logger")
@@ -23,9 +24,10 @@ def connectToDatabase():
         if not database.isValid():
             logger.error("Cannot add database")
 
-    write_dir = QDir("")
+    app_data = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppDataLocation)
+    write_dir = QDir(app_data)
     if not write_dir.mkpath("."):
-        logger.error("Failed to create writable directory")
+        logger.error(f"Failed to create writable directory {app_data}")
 
     # Ensure that we have a writable location on all devices.
     abs_path = write_dir.absolutePath()
@@ -41,12 +43,17 @@ def connectToDatabase():
 
 if __name__ == "__main__":
     app = QGuiApplication()
+    QCoreApplication.setOrganizationName("QtProject")
+    QCoreApplication.setApplicationName("Chat Tutorial")
+
     connectToDatabase()
 
     engine = QQmlApplicationEngine()
-    engine.load(QUrl("chat.qml"))
+    engine.addImportPath(sys.path[0])
+    engine.loadFromModule("Main", "Main")
 
     if not engine.rootObjects():
         sys.exit(-1)
 
     app.exec()
+    del engine
index eee3f807e01e09a2d63c943436988db0bf9a68cc..0edc61193c9b84490eec1587f50a36a80132fc94 100644 (file)
@@ -16,7 +16,7 @@ The database contains a single line to mock the beginning of a conversation.
 
 .. literalinclude:: sqlDialog.py
    :linenos:
-   :lines: 4-43
+   :lines: 5-44
 
 The ``SqlConversationModel`` class offers the read-only data model required for the non-editable
 contacts list. It derives from the :ref:`QSqlQueryModel` class, which is the logical choice for
@@ -28,14 +28,7 @@ of a chat application.
 
 .. literalinclude:: sqlDialog.py
    :linenos:
-   :lines: 47-59
-
-In ``setRecipient()``, you set a filter over the returned results from the database, and
-emit a signal every time the recipient of the message changes.
-
-.. literalinclude:: sqlDialog.py
-   :linenos:
-   :lines: 61-70
+   :lines: 48-60
 
 The ``data()`` function falls back to ``QSqlTableModel``'s implementation if the role is not a
 custom user role.
@@ -44,18 +37,16 @@ that field, and then use that index to find the value to be returned.
 
 .. literalinclude:: sqlDialog.py
    :linenos:
-   :lines: 72-79
+   :lines: 62-69
 
 
 In ``roleNames()``, we return a Python dictionary with our custom role and role names as key-values
 pairs, so we can use these roles in QML.
 Alternatively, it can be useful to declare an Enum to hold all of the role values.
-Note that ``names`` has to be a hash to be used as a dictionary key,
-and that's why we're using the ``hash`` function.
 
 .. literalinclude:: sqlDialog.py
    :linenos:
-   :lines: 81-95
+   :lines: 71-78
 
 The ``send_message()`` function uses the given recipient and message to insert a new record into
 the database.
@@ -64,14 +55,14 @@ since all the changes will be cached in the model until you do so.
 
 .. literalinclude:: sqlDialog.py
    :linenos:
-   :lines: 97-116
+   :lines: 80-99
 
-chat.qml
+Main.qml
 --------
 
-Let's look at the ``chat.qml`` file.
+Let's look at the ``Main.qml`` file.
 
-.. literalinclude:: chat.qml
+.. literalinclude:: Main/Main.qml
    :linenos:
    :lines: 4-6
 
@@ -84,9 +75,9 @@ Next, import the Qt Quick Controls module.
 Among other things, this provides access to ``ApplicationWindow``, which replaces the existing
 root type, Window:
 
-Let's step through the ``chat.qml`` file.
+Let's step through the ``Main/Main.qml`` file.
 
-.. literalinclude:: chat.qml
+.. literalinclude:: Main/Main.qml
    :linenos:
    :lines: 9-14
 
@@ -101,7 +92,7 @@ Once we've set these, we have a properly sized, empty window ready to be filled
 Because we are exposing the :code:`SqlConversationModel` class to QML, we will
 declare a component to access it:
 
-.. literalinclude:: chat.qml
+.. literalinclude:: Main/Main.qml
    :linenos:
    :lines: 16-18
 
@@ -113,11 +104,11 @@ There are two ways of laying out items in QML: `Item Positioners`_ and `Qt Quick
   resizable user interfaces.
   Below, we use `ColumnLayout`_ to vertically lay out a `ListView`_ and a `Pane`_.
 
-  .. literalinclude:: chat.qml
+  .. literalinclude:: Main/Main.qml
      :linenos:
      :lines: 20-23
 
-  .. literalinclude:: chat.qml
+  .. literalinclude:: Main/Main.qml
      :linenos:
      :lines: 72-74
 
@@ -148,7 +139,7 @@ remaining space that is left after accommodating the Pane.
 
 Let's look at the ``Listview`` in detail:
 
-.. literalinclude:: chat.qml
+.. literalinclude:: Main/Main.qml
    :linenos:
    :lines: 23-70
 
@@ -177,7 +168,7 @@ At the bottom of the screen, we place a `TextArea`_ item to allow multi-line tex
 button to send the message.
 We use Pane to cover the area under these two items:
 
-.. literalinclude:: chat.qml
+.. literalinclude:: Main/Main.qml
    :linenos:
    :lines: 72-96
 
@@ -194,6 +185,10 @@ recipient and one possible sender for this conversation we're just using strings
 .. _displayMarginEnd: https://doc.qt.io/qt-5/qml-qtquick-listview.html#displayMarginEnd-prop
 .. _TextArea: https://doc.qt.io/qt-5/qml-qtquick-controls2-textarea.html
 
+``Main.qml`` needs to be put into a directory named :code:`Main` along
+with a file named ``qmldir`` to describe a basic QML module:
+
+.. literalinclude:: Main/qmldir
 
 main.py
 -------
@@ -203,14 +198,14 @@ messages levels that our application will generate (errors, warnings, and inform
 
 .. literalinclude:: main.py
    :linenos:
-   :lines: 4-16
+   :lines: 5-17
 
 ``connectToDatabase()`` creates a connection with the SQLite database, creating the actual file
 if it doesn't already exist.
 
 .. literalinclude:: main.py
    :linenos:
-   :lines: 19-39
+   :lines: 20-41
 
 A few interesting things happen in the ``main`` function:
 
@@ -227,6 +222,6 @@ Finally, the Qt application runs, and your program starts.
 
 .. literalinclude:: main.py
    :linenos:
-   :lines: 42-52
+   :lines: 45-59
 
 .. image:: example_list_view.png
index d728aee59f64b675b5aa0103e3a1d87572d808e1..88e60e70f62281c209793e361766e26e04f1cd6d 100644 (file)
@@ -1,5 +1,6 @@
 # Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
 
 import datetime
 import logging
@@ -58,17 +59,6 @@ class SqlConversationModel(QSqlTableModel):
         self.select()
         logging.debug("Table was loaded successfully.")
 
-    def setRecipient(self, recipient):
-        if recipient == self.recipient:
-            pass
-
-        self.recipient = recipient
-
-        filter_str = (f"(recipient = '{self.recipient}' AND author = 'Me') OR "
-                      f"(recipient = 'Me' AND author='{self.recipient}')")
-        self.setFilter(filter_str)
-        self.select()
-
     def data(self, index, role):
         if role < Qt.UserRole:
             return QSqlTableModel.data(self, index, role)
@@ -81,18 +71,11 @@ class SqlConversationModel(QSqlTableModel):
     def roleNames(self):
         """Converts dict to hash because that's the result expected
         by QSqlTableModel"""
-        names = {}
-        author = "author".encode()
-        recipient = "recipient".encode()
-        timestamp = "timestamp".encode()
-        message = "message".encode()
-
-        names[hash(Qt.UserRole)] = author
-        names[hash(Qt.UserRole + 1)] = recipient
-        names[hash(Qt.UserRole + 2)] = timestamp
-        names[hash(Qt.UserRole + 3)] = message
-
-        return names
+
+        return {int(Qt.UserRole): b"author",
+                Qt.UserRole + 1: b"recipient",
+                Qt.UserRole + 2: b"timestamp",
+                Qt.UserRole + 3: b"message"}
 
     # This is a workaround because PySide doesn't provide Q_INVOKABLE
     # So we declare this as a Slot to be able to call it  from QML
index 048001f81cbbcb6a57b25599066ba27b2f659bd3..9aeab6996bb838c85e2ab8be914660a9cdc6cbde 100644 (file)
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "dynamicqmetaobject.h"
+#include "pysidelogging_p.h"
 #include "pysideqobject.h"
 #include "pysidesignal.h"
 #include "pysidesignal_p.h"
@@ -277,6 +278,15 @@ int MetaObjectBuilderPrivate::getPropertyNotifyId(PySideProperty *property) cons
     return notifyId;
 }
 
+static QByteArray msgInvalidPropertyType(const QByteArray &className,
+                                         const QByteArray &propertyName,
+                                         const QByteArray &propertyType)
+{
+    return "QMetaObjectBuilder: Failed to add property \""_ba + propertyName
+           + "\" to \""_ba + className + "\": Invalid property type \""
+           + propertyType + "\"."_ba;
+}
+
 QMetaPropertyBuilder
     MetaObjectBuilderPrivate::createProperty(PySideProperty *property,
                                              const QByteArray &propertyName)
@@ -302,8 +312,13 @@ QMetaPropertyBuilder
             }
         }
     }
-    return builder->addProperty(propertyName, property->d->typeName,
-                                propertyNotifyId);
+    const auto metaType = QMetaType::fromName(property->d->typeName);
+    if (!metaType.isValid()) {
+        const auto &msg = msgInvalidPropertyType(m_builder->className(), propertyName,
+                                                 property->d->typeName);
+         PyErr_WarnEx(PyExc_RuntimeWarning, msg.constData(), 0);
+    }
+    return builder->addProperty(propertyName, property->d->typeName, metaType, propertyNotifyId);
 }
 
 int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName,
index 51070b4ad06309a05ed59299f9f53e74a069f1bd..57c7881307aa43e76b6b1b019b55775ff0a98aa3 100644 (file)
@@ -88,12 +88,16 @@ DynamicSlotDataV2::DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *paren
 
     if (PyMethod_Check(callback)) {
         m_isMethod = true;
-        // To avoid increment instance reference keep the callback information
+        // A method given by "signal.connect(foo.method)" is a temporarily created
+        // callable/partial function where self is bound as a first parameter.
+        // It can be split into self and the function. Keeping a reference on
+        // the callable itself would prevent object deletion. Instead, keep a
+        // reference on the function and listen for destruction of the object
+        // using a weak reference with notification.
         m_callback = PyMethod_GET_FUNCTION(callback);
         Py_INCREF(m_callback);
         m_pythonSelf = PyMethod_GET_SELF(callback);
 
-        //monitor class from method lifetime
         m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this);
     } else if (PySide::isCompiledMethod(callback)) {
         // PYSIDE-1523: PyMethod_Check is not accepting compiled form, we just go by attributes.
index 3a06a03e4d7b3fc6eee54e8dbb0c3091d4c06f53..48ba52261b685c7227e4591cc2f280e7d4f6dd32 100644 (file)
 #include <optional>
 #include <typeinfo>
 
+#ifdef Q_OS_WIN
+#  include <conio.h>
+#else
+#  include <QtCore/QDeadlineTimer>
+#  include <QtCore/private/qcore_unix_p.h>
+#endif
+
 using namespace Qt::StringLiterals;
 
 static QStack<PySide::CleanupFunction> cleanupFunctionList;
@@ -516,6 +523,36 @@ void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject * /* kwds *
     PySide::Feature::Enable(true);
 }
 
+extern "C" {
+static int qAppInputHook()
+{
+    auto *app = QCoreApplication::instance();
+    if (app == nullptr || app->thread() != QThread::currentThread())
+        return 0;
+#ifndef Q_OS_WIN
+    // Check for press on stdin (file descriptor 0)
+    pollfd stdinPfd =  qt_make_pollfd(0, POLLIN);
+    while (qt_safe_poll(&stdinPfd, 1, QDeadlineTimer{1}) == 0)
+        QCoreApplication::processEvents({}, 50000);
+#else
+    while (_kbhit() == 0)
+        QCoreApplication::processEvents({}, 50000);
+#endif
+    return 0;
+}
+} // extern "C"
+
+static void unregisterQAppInputHook()
+{
+    PyOS_InputHook = nullptr;
+}
+
+static void registerQAppInputHook()
+{
+    PyOS_InputHook = qAppInputHook;
+    qAddPostRoutine(unregisterQAppInputHook);
+}
+
 void initQApp()
 {
     /*
@@ -529,8 +566,10 @@ void initQApp()
      * I would appreciate very much if someone could explain or even fix
      * this issue. It exists only when a pre-existing application exists.
      */
-    if (!qApp)
+    if (qApp == nullptr) {
+        registerQAppInputHook();
         Py_DECREF(MakeQAppWrapper(nullptr));
+    }
 
     // PYSIDE-1470: Register a function to destroy an application from shiboken.
     setDestroyQApplication(destroyQCoreApplication);
index 11e07cb048db1d4a8c3affdf7dbcc6b1d616a532..5ab4d40c33f3a9f96ae0a9bc9693f51333657f64 100644 (file)
@@ -653,8 +653,11 @@ static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key)
 static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signature)
 {
     if (PyErr_Occurred() != nullptr) { // avoid "%S" invoking str() when an error is set.
+        PyObject *exc{}, *inst{}, *tb{};
+        PyErr_Fetch(&exc, &inst, &tb);
         PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%s) from signal \"%s\".",
                          Py_TYPE(aSlot)->tp_name, signature.constData());
+        PyErr_Restore(exc, inst, tb);
     } else {
         PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%S) from signal \"%s\".",
                          aSlot, signature.constData());
index d23156a9d782a0e55bbf4e40f98d5188d9b6f727..dc8bdf435321bbeafc89c5f0b8b0f6b4a75b1e33 100644 (file)
@@ -17,6 +17,7 @@
 #include <QtCore/QVariant>
 
 #include <string_view>
+#include <utility>
 
 using namespace Qt::StringLiterals;
 
@@ -123,26 +124,29 @@ static bool runPyScriptFile(const QString &fileName, QString *errorMessage)
     return ok;
 }
 
+static std::pair<int, int> pythonVersion()
+{
+    // read environment set by pyside_tool.py
+    bool majorOk{};
+    bool minorOk{};
+    const int majorVersion = qEnvironmentVariableIntValue("PY_MAJOR_VERSION", &majorOk);
+    const int minorVersion = qEnvironmentVariableIntValue("PY_MINOR_VERSION", &minorOk);
+    if (majorOk && minorVersion)
+        return {majorVersion, minorVersion};
+    return {PY_MAJOR_VERSION, PY_MINOR_VERSION};
+}
+
 static void initVirtualEnvironment()
 {
     static const char virtualEnvVar[] = "VIRTUAL_ENV";
-    // As of Python 3.8/Windows, Python is no longer able to run stand-alone in
-    // a virtualenv due to missing libraries. Add the path to the modules
-    // instead. macOS seems to be showing the same issues.
+    // Since Python 3.8 (Windows, macOS), Python is no longer able to run stand
+    // -alone in a virtualenv due to missing libraries. Add the path to the modules
+    // instead.
 
     const auto os = QOperatingSystemVersion::currentType();
 
-    bool ok;
-    int majorVersion = qEnvironmentVariableIntValue("PY_MAJOR_VERSION", &ok);
-    int minorVersion = qEnvironmentVariableIntValue("PY_MINOR_VERSION", &ok);
-    if (!ok) {
-        majorVersion = PY_MAJOR_VERSION;
-        minorVersion = PY_MINOR_VERSION;
-    }
-
     if (!qEnvironmentVariableIsSet(virtualEnvVar)
-        || (os != QOperatingSystemVersion::MacOS && os != QOperatingSystemVersion::Windows)
-        || (majorVersion == 3 && minorVersion < 8)) {
+        || (os != QOperatingSystemVersion::MacOS && os != QOperatingSystemVersion::Windows)) {
         return;
     }
 
@@ -155,11 +159,13 @@ static void initVirtualEnvironment()
     case QOperatingSystemVersion::Windows:
         pythonPath.append(virtualEnvPath + R"(\Lib\site-packages)");
         break;
-    case QOperatingSystemVersion::MacOS:
+    case QOperatingSystemVersion::MacOS: {
+        const auto version = pythonVersion();
         pythonPath.append(virtualEnvPath + "/lib/python"_ba +
-                          QByteArray::number(majorVersion) + '.'
-                          + QByteArray::number(minorVersion)
+                          QByteArray::number(version.first) + '.'
+                          + QByteArray::number(version.second)
                           + "/site-packages"_ba);
+    }
         break;
     default:
         break;
diff --git a/sources/pyside6/tests/QtAsyncio/bug_2790.py b/sources/pyside6/tests/QtAsyncio/bug_2790.py
new file mode 100644 (file)
index 0000000..9fd152b
--- /dev/null
@@ -0,0 +1,47 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for QtAsyncio'''
+
+import unittest
+import asyncio
+
+import PySide6.QtAsyncio as QtAsyncio
+
+
+class QAsyncioTestCaseBug2790(unittest.TestCase):
+
+    async def producer(self, products: list[str]):
+        while True:
+            products.append("product")
+            await asyncio.sleep(2)
+
+    async def task(self, outputs: list[str]):
+        products = []
+        asyncio.ensure_future(self.producer(products))
+        for _ in range(6):
+            try:
+                async with asyncio.timeout(0.5):
+                    while len(products) == 0:
+                        await asyncio.sleep(0)
+                    outputs.append(products.pop(0))
+            except TimeoutError:
+                outputs.append("Timeout")
+
+    def test_timeout(self):
+        # The Qt event loop (and thus QtAsyncio) does not guarantee that events
+        # will be processed in the order they were posted, so there is two
+        # possible outputs for this test.
+        outputs_expected_1 = ["product", "Timeout", "Timeout", "Timeout", "Timeout", "product"]
+        outputs_expected_2 = ["product", "Timeout", "Timeout", "Timeout", "product", "Timeout"]
+
+        outputs_real = []
+
+        QtAsyncio.run(self.task(outputs_real), keep_running=False)
+
+        self.assertTrue(outputs_real in [outputs_expected_1, outputs_expected_2])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/sources/pyside6/tests/QtAsyncio/bug_2799.py b/sources/pyside6/tests/QtAsyncio/bug_2799.py
new file mode 100644 (file)
index 0000000..eb86c7d
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+"""Test cases for QtAsyncio"""
+
+import unittest
+import asyncio
+import sys
+
+import PySide6.QtAsyncio as QtAsyncio
+
+
+@unittest.skipIf(sys.version_info < (3, 11), "Requires ExceptionGroup")
+class QAsyncioTestCaseBug2799(unittest.TestCase):
+    async def job(self):
+        await asyncio.sleep(1)
+
+    async def main(self):
+        async with asyncio.TaskGroup() as tg:
+            tg.create_task(self.job())
+            raise RuntimeError()
+
+    def test_exception_group(self):
+        with self.assertRaises(ExceptionGroup):
+            QtAsyncio.run(self.main(), keep_running=False)
+
+
+if __name__ == "__main__":
+    unittest.main()
index aa8ce4718bdb2b3619f862771bb6f7b9e39cecc6..f521e4238915a075c7aaef8d1dfad481443d9290 100644 (file)
@@ -5,11 +5,23 @@
 
 import asyncio
 import unittest
+import sys
 
 import PySide6.QtAsyncio as QtAsyncio
 
 
+@unittest.skipIf(sys.version_info < (3, 11), "Requires ExceptionGroup")
 class QAsyncioTestCaseCancelTaskGroup(unittest.TestCase):
+
+    """
+    PYSIDE-2644: If a task was cancelled, then a new future created from this
+    task should be cancelled as well. Otherwise, in some scenarios like a loop
+    inside the task and with bad timing, if the new future is not cancelled,
+    the task would continue running in this loop despite having been cancelled.
+    This bad timing can occur especially if the first future finishes very
+    quickly.
+    """
+
     def setUp(self) -> None:
         super().setUp()
         # We only reach the end of the loop if the task is not cancelled.
@@ -47,7 +59,7 @@ class QAsyncioTestCaseCancelTaskGroup(unittest.TestCase):
         for coro in coros:
             try:
                 QtAsyncio.run(self.main(coro), keep_running=False)
-            except ExceptionGroup as e:
+            except ExceptionGroup as e:  # noqa: F821
                 self.assertEqual(len(e.exceptions), 1)
                 self.assertIsInstance(e.exceptions[0], RuntimeError)
                 self.assertFalse(self._loop_end_reached)
diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_uncancel.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_uncancel.py
new file mode 100644 (file)
index 0000000..0366228
--- /dev/null
@@ -0,0 +1,61 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+"""Test cases for QtAsyncio"""
+
+import unittest
+import asyncio
+
+import PySide6.QtAsyncio as QtAsyncio
+
+
+class QAsyncioTestCaseUncancel(unittest.TestCase):
+    """ https://superfastpython.com/asyncio-cancel-task-cancellation """
+
+    async def worker(self, outputs: list[str]):
+        # Ensure the task always gets done.
+        while True:
+            try:
+                await asyncio.sleep(2)
+                outputs.append("Task sleep completed normally")
+                break
+            except asyncio.CancelledError:
+                outputs.append("Task is cancelled, ignore and try again")
+                asyncio.current_task().uncancel()
+
+    async def main(self, outputs: list[str]):
+        task = asyncio.create_task(self.worker(outputs))
+        # Allow the task to run briefly.
+        await asyncio.sleep(0.5)
+        task.cancel()
+        try:
+            await task
+        except asyncio.CancelledError:
+            outputs.append("Task was cancelled")
+
+        cancelling = task.cancelling()
+        self.assertEqual(cancelling, 0)
+        outputs.append(f"Task cancelling: {cancelling}")
+
+        cancelled = task.cancelled()
+        self.assertFalse(cancelled)
+        outputs.append(f"Task cancelled: {cancelled}")
+
+        done = task.done()
+        self.assertTrue(done)
+        outputs.append(f"Task done: {done}")
+
+    def test_uncancel(self):
+        outputs_expected = []
+        outputs_real = []
+
+        asyncio.run(self.main(outputs_real))
+        QtAsyncio.run(self.main(outputs_expected), keep_running=False)
+
+        self.assertIsNotNone(outputs_real)
+        self.assertEqual(outputs_real, outputs_expected)
+
+
+if __name__ == "__main__":
+    unittest.main()
index 6a63edb859b88b151c2b88f4a864b18a289b2953..83c77616c296e2ade38592ab553959494705ada5 100644 (file)
@@ -32,15 +32,16 @@ class QMimeDatabaseTest(unittest.TestCase):
         self.assertTrue(s1.isValid())
         self.assertEqual(s1.name(), "text/plain")
 
-        krita = db.mimeTypeForName("application/x-krita")
-        self.assertTrue(krita.isValid())
+        # Removed because of the move of to the Tika mimetypes.
+        # krita = db.mimeTypeForName("application/x-krita")
+        # self.assertTrue(krita.isValid())
 
         rdf = db.mimeTypeForName("application/rdf+xml")
         self.assertTrue(rdf.isValid())
         self.assertEqual(rdf.name(), "application/rdf+xml")
         self.assertTrue(rdf.comment())
         if "en" in QLocale().name():
-            self.assertEqual(rdf.comment(), "RDF file")
+            self.assertTrue(rdf.comment() in ("RDF file", "XML syntax for RDF graphs"))
 
         bzip2 = db.mimeTypeForName("application/x-bzip2")
         self.assertTrue(bzip2.isValid())
index af359e5259b92b2ab56dc61e89e441d17d89cf79..5959289c76b15e27a453f9ebdd006c3cd9ef28ed 100644 (file)
@@ -47,7 +47,15 @@ class testAudioDevices(UsesQApplication):
             return
         size = 256
         byte_array = QByteArray(size, '7')
-        buffer = QAudioBuffer(byte_array, self._devices[0].preferredFormat())
+        device = self._devices[0]
+        format = device.preferredFormat()
+        # Observed to be "Unknown" on Linux
+        if format.sampleFormat() == QAudioFormat.SampleFormat.Unknown:
+            sample_formats = device.supportedSampleFormats()
+            if sample_formats:
+                format.setSampleFormat(sample_formats[0])
+                format.setSampleRate(48000)
+        buffer = QAudioBuffer(byte_array, format)
         self.assertEqual(buffer.byteCount(), 256)
         data = buffer.data()
         actual_byte_array = QByteArray(bytearray(data))
diff --git a/sources/pyside6/tests/QtWebView/CMakeLists.txt b/sources/pyside6/tests/QtWebView/CMakeLists.txt
new file mode 100644 (file)
index 0000000..63f3136
--- /dev/null
@@ -0,0 +1,4 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Tests to be added later
index 67ad14d68b8aa4d6d13cf8fbd34cea3993ebadcb..b896bfff562f38042bd57be54aad30ec1b13bec1 100644 (file)
@@ -16,8 +16,9 @@ from init_paths import init_test_paths
 init_test_paths(False)
 
 from PySide6.QtCore import Signal
-from PySide6.QtWidgets import QApplication, QWidget
-from PySide6 import QtWidgets
+from PySide6.QtWidgets import QApplication, QWidget  # noqa F401
+
+from helper.usesqapplication import UsesQApplication
 
 
 class Harness(QWidget):
@@ -58,13 +59,7 @@ class _Under(QWidget):
         self.method___result = self.sender()
 
 
-class TestMangle(unittest.TestCase):
-
-    def setUp(self):
-        QApplication()
-
-    def tearDown(self):
-        qApp.shutdown()
+class TestMangle(UsesQApplication):
 
     def testPrivateMangle(self):
         harness = Harness()
index f84eec1eb7e535bf722e2f0914ece48fda29f522..8c3bb60fcecc944efbdb20b285515c853b602b9e 100644 (file)
@@ -151,7 +151,7 @@ class TestPySide6DeployWidgets(DeployTestBase):
         self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
         self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
         self.assertEqual(config_obj.get_value("python", "packages"),
-                         "Nuitka==2.3.2")
+                         "Nuitka==2.4.8")
         self.assertEqual(config_obj.get_value("qt", "qml_files"), "")
         equ_base = "--quiet --noinclude-qt-translations"
         equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
@@ -174,6 +174,16 @@ class TestPySide6DeployWidgets(DeployTestBase):
             self.deploy.main(main_file=fake_main_file, config_file=self.config_file)
         self.assertTrue("Directory does not contain main.py file." in str(context.exception))
 
+    def testStandaloneMode(self, mock_plugins):
+        mock_plugins.return_value = self.all_plugins
+        # remove --onefile from self.expected_run_cmd and replace it with --standalone
+        self.expected_run_cmd = self.expected_run_cmd.replace(" --onefile", " --standalone")
+        # test standalone mode
+        original_output = self.deploy.main(self.main_file, mode="standalone", dry_run=True,
+                                           force=True)
+
+        self.assertEqual(original_output, self.expected_run_cmd)
+
 
 @unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
                  "Test only works on macOS version 12+")
@@ -257,7 +267,7 @@ class TestPySide6DeployQml(DeployTestBase):
         self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
         self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
         self.assertEqual(config_obj.get_value("python", "packages"),
-                         "Nuitka==2.3.2")
+                         "Nuitka==2.4.8")
         self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml")
         equ_base = "--quiet --noinclude-qt-translations"
         equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
@@ -396,5 +406,41 @@ class TestPySide6DeployWebEngine(DeployTestBase):
         self.assertEqual(obtained_modules, expected_modules)
 
 
+@unittest.skipIf(sys.platform != "win32", "Test only works on Windows")
+class TestLongCommand(DeployTestBase):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        example_qml = cls.example_root / "qml" / "editingmodel"
+        cls.temp_example_qml = Path(
+            shutil.copytree(example_qml, Path(cls.temp_dir) / "editingmodel")
+        ).resolve()
+
+    def setUp(self):
+        os.chdir(self.temp_example_qml)
+        self.main_file = self.temp_example_qml / "main.py"
+
+    @patch('deploy_lib.nuitka_helper.os.remove')
+    @patch("deploy_lib.config.run_qmlimportscanner")
+    @patch('deploy.DesktopConfig.qml_files', new_callable=mock.PropertyMock)
+    def test_main_with_mocked_qml_files(self, mock_qml_files, mock_qmlimportscanner, mock_remove):
+        mock_qmlimportscanner.return_value = ["QtQuick"]
+        mock_qml_files.return_value = [self.temp_example_qml / "MovingRectangle.qml"
+                                       for _ in range(500)]
+
+        command_str = self.deploy.main(self.main_file, force=True, keep_deployment_files=True,
+                                       dry_run=True)
+        mock_remove.assert_called_once()
+
+        # check if command_str ends with deploy_main.py
+        self.assertTrue(command_str.endswith("deploy_main.py"))
+
+        # check if deploy_main.py startes with # nuitka-project:
+        with open(self.temp_example_qml / "deploy_main.py", "r") as file:
+            # check if 517 lines start with # nuitka-project:
+            self.assertEqual(len([line for line in file.readlines()
+                                  if line.startswith("# nuitka-project:")]), 517)
+
+
 if __name__ == "__main__":
     unittest.main()
index 7a532df213f67620458ef6bf184284509405a51b..e9ed38490536e0b8ffd239c51bec45f752bc2def 100644 (file)
@@ -1,5 +1,5 @@
 set(shiboken_MAJOR_VERSION "6")
 set(shiboken_MINOR_VERSION "7")
-set(shiboken_MICRO_VERSION "2")
+set(shiboken_MICRO_VERSION "3")
 set(shiboken_PRE_RELEASE_VERSION_TYPE "")
 set(shiboken_PRE_RELEASE_VERSION "")
index 0720fca7eb97926fc3c642a8c434f2ad58c05c7b..7f5d12eba68f3ddbf503cb44358827293908eb42 100644 (file)
@@ -81,7 +81,7 @@ QTextStream &operator<<(QTextStream &str, const RejectEntry &re)
     return str;
 }
 
-static void applyCachedFunctionModifications(AbstractMetaFunction *metaFunction,
+static void applyCachedFunctionModifications(const AbstractMetaFunctionPtr &metaFunction,
                                              const FunctionModificationList &functionMods)
 {
     for (const FunctionModification &mod : functionMods) {
@@ -317,7 +317,7 @@ void AbstractMetaBuilderPrivate::traverseOperatorFunction(const FunctionModelIte
         return;
     }
 
-    AbstractMetaFunction *metaFunction = traverseFunction(item, baseoperandClass);
+    auto metaFunction = traverseFunction(item, baseoperandClass);
     if (metaFunction == nullptr)
         return;
 
@@ -350,7 +350,7 @@ void AbstractMetaBuilderPrivate::traverseOperatorFunction(const FunctionModelIte
     }
     metaFunction->setFlags(flags);
     metaFunction->setAccess(Access::Public);
-    AbstractMetaClass::addFunction(baseoperandClass, AbstractMetaFunctionCPtr(metaFunction));
+    AbstractMetaClass::addFunction(baseoperandClass, metaFunction);
     if (!metaFunction->arguments().isEmpty()) {
         const auto include = metaFunction->arguments().constFirst().type().typeEntry()->include();
         baseoperandClass->typeEntry()->addArgumentInclude(include);
@@ -371,7 +371,7 @@ bool AbstractMetaBuilderPrivate::traverseStreamOperator(const FunctionModelItem
    if (streamedClass == nullptr)
        return false;
 
-   AbstractMetaFunction *streamFunction = traverseFunction(item, streamedClass);
+   auto streamFunction = traverseFunction(item, streamedClass);
    if (!streamFunction)
        return false;
 
@@ -401,7 +401,7 @@ bool AbstractMetaBuilderPrivate::traverseStreamOperator(const FunctionModelItem
         funcClass = streamClass;
     }
 
-    AbstractMetaClass::addFunction(funcClass, AbstractMetaFunctionCPtr(streamFunction));
+    AbstractMetaClass::addFunction(funcClass, streamFunction);
     auto funcTe = funcClass->typeEntry();
     if (funcClass == streamClass)
         funcTe->addArgumentInclude(streamedClass->typeEntry()->include());
@@ -555,17 +555,16 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom,
         if (!funcEntry || !funcEntry->generateCode())
             continue;
 
-        AbstractMetaFunction *metaFunc = traverseFunction(func, nullptr);
-        if (!metaFunc)
+        auto metaFuncPtr = traverseFunction(func, nullptr);
+        if (!metaFuncPtr)
             continue;
 
-        AbstractMetaFunctionCPtr metaFuncPtr(metaFunc);
-        if (!funcEntry->hasSignature(metaFunc->minimalSignature()))
+        if (!funcEntry->hasSignature(metaFuncPtr->minimalSignature()))
             continue;
 
-        metaFunc->setTypeEntry(funcEntry);
-        applyFunctionModifications(metaFunc);
-        metaFunc->applyTypeModifications();
+        metaFuncPtr->setTypeEntry(funcEntry);
+        applyFunctionModifications(metaFuncPtr);
+        metaFuncPtr->applyTypeModifications();
 
         setInclude(funcEntry, func->fileName());
 
@@ -1348,7 +1347,7 @@ void AbstractMetaBuilderPrivate::traverseFields(const ScopeModelItem &scope_item
     }
 }
 
-void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(AbstractMetaFunction *metaFunction)
+void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(const AbstractMetaFunctionPtr &metaFunction)
 {
     if (!metaFunction->isConversionOperator())
         return;
@@ -1372,13 +1371,13 @@ void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(AbstractMetaF
     metaFunction->setType(metaType);
 }
 
-AbstractMetaFunctionRawPtrList
+AbstractMetaFunctionList
     AbstractMetaBuilderPrivate::classFunctionList(const ScopeModelItem &scopeItem,
                                                   AbstractMetaClass::Attributes *constructorAttributes,
                                                   const AbstractMetaClassPtr &currentClass)
 {
     *constructorAttributes = {};
-    AbstractMetaFunctionRawPtrList result;
+    AbstractMetaFunctionList result;
     const FunctionList &scopeFunctionList = scopeItem->functions();
     result.reserve(scopeFunctionList.size());
     const bool isNamespace = currentClass->isNamespace();
@@ -1388,7 +1387,7 @@ AbstractMetaFunctionRawPtrList
         } else if (function->isSpaceshipOperator() && !function->isDeleted()) {
             if (currentClass)
                 AbstractMetaClass::addSynthesizedComparisonOperators(currentClass);
-        } else if (auto *metaFunction = traverseFunction(function, currentClass)) {
+        } else if (auto metaFunction = traverseFunction(function, currentClass)) {
             result.append(metaFunction);
         } else if (!function->isDeleted() && function->functionType() == CodeModel::Constructor) {
             auto arguments = function->arguments();
@@ -1404,11 +1403,11 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt
                                                    const AbstractMetaClassPtr &metaClass)
 {
     AbstractMetaClass::Attributes constructorAttributes;
-    const AbstractMetaFunctionRawPtrList functions =
+    const AbstractMetaFunctionList functions =
         classFunctionList(scopeItem, &constructorAttributes, metaClass);
     metaClass->setAttributes(metaClass->attributes() | constructorAttributes);
 
-    for (AbstractMetaFunction *metaFunction : functions) {
+    for (const auto &metaFunction : functions) {
         if (metaClass->isNamespace())
             metaFunction->setCppAttribute(FunctionAttribute::Static);
 
@@ -1461,23 +1460,20 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt
         if (!metaFunction->isDestructor()
             && !(metaFunction->isPrivate() && metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction)) {
 
-            if (metaFunction->isSignal() && metaClass->hasSignal(metaFunction))
-                qCWarning(lcShiboken, "%s", qPrintable(msgSignalOverloaded(metaClass, metaFunction)));
+            if (metaFunction->isSignal() && metaClass->hasSignal(metaFunction.get()))
+                qCWarning(lcShiboken, "%s", qPrintable(msgSignalOverloaded(metaClass,
+                                                                           metaFunction.get())));
 
             if (metaFunction->isConversionOperator())
                 fixReturnTypeOfConversionOperator(metaFunction);
 
-            AbstractMetaClass::addFunction(metaClass, AbstractMetaFunctionCPtr(metaFunction));
+            AbstractMetaClass::addFunction(metaClass, metaFunction);
             applyFunctionModifications(metaFunction);
         } else if (metaFunction->isDestructor()) {
             metaClass->setHasPrivateDestructor(metaFunction->isPrivate());
             metaClass->setHasProtectedDestructor(metaFunction->isProtected());
             metaClass->setHasVirtualDestructor(metaFunction->isVirtual());
         }
-        if (!metaFunction->ownerClass()) {
-            delete metaFunction;
-            metaFunction = nullptr;
-        }
     }
 
     fillAddedFunctions(metaClass);
@@ -1539,7 +1535,7 @@ QStringList AbstractMetaBuilder::definitionNames(const QString &name,
     return result;
 }
 
-void AbstractMetaBuilderPrivate::applyFunctionModifications(AbstractMetaFunction *func)
+void AbstractMetaBuilderPrivate::applyFunctionModifications(const AbstractMetaFunctionPtr &func)
 {
     AbstractMetaFunction& funcRef = *func;
     for (const FunctionModification &mod : func->modifications(func->implementingClass())) {
@@ -1677,15 +1673,14 @@ static AbstractMetaFunction::FunctionType functionTypeFromName(const QString &);
 bool AbstractMetaBuilderPrivate::traverseAddedGlobalFunction(const AddedFunctionPtr &addedFunc,
                                                              QString *errorMessage)
 {
-    AbstractMetaFunction *metaFunction =
-        traverseAddedFunctionHelper(addedFunc, nullptr, errorMessage);
+    auto metaFunction = traverseAddedFunctionHelper(addedFunc, nullptr, errorMessage);
     if (metaFunction == nullptr)
         return false;
-    m_globalFunctions << AbstractMetaFunctionCPtr(metaFunction);
+    m_globalFunctions << metaFunction;
     return true;
 }
 
-AbstractMetaFunction *
+AbstractMetaFunctionPtr
     AbstractMetaBuilderPrivate::traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc,
                                                             const AbstractMetaClassPtr &metaClass /* = {} */,
                                                             QString *errorMessage)
@@ -1696,10 +1691,10 @@ AbstractMetaFunction *
             msgAddedFunctionInvalidReturnType(addedFunc->name(),
                                               addedFunc->returnType().qualifiedName(),
                                               *errorMessage, metaClass);
-        return nullptr;
+        return {};
     }
 
-    auto *metaFunction = new AbstractMetaFunction(addedFunc);
+    auto metaFunction = std::make_shared<AbstractMetaFunction>(addedFunc);
     metaFunction->setType(returnType.value());
     metaFunction->setFunctionType(functionTypeFromName(addedFunc->name()));
 
@@ -1717,8 +1712,7 @@ AbstractMetaFunction *
                 msgAddedFunctionInvalidArgType(addedFunc->name(),
                                                arg.typeInfo.qualifiedName(), i + 1,
                                                *errorMessage, metaClass);
-            delete metaFunction;
-            return nullptr;
+            return {};
         }
         type->decideUsagePattern();
 
@@ -1775,8 +1769,7 @@ bool AbstractMetaBuilderPrivate::traverseAddedMemberFunction(const AddedFunction
                                                              const AbstractMetaClassPtr &metaClass,
                                                              QString *errorMessage)
 {
-    AbstractMetaFunction *metaFunction =
-        traverseAddedFunctionHelper(addedFunc, metaClass, errorMessage);
+    auto metaFunction = traverseAddedFunctionHelper(addedFunc, metaClass, errorMessage);
     if (metaFunction == nullptr)
         return false;
 
@@ -1796,12 +1789,13 @@ bool AbstractMetaBuilderPrivate::traverseAddedMemberFunction(const AddedFunction
 
     metaFunction->setDeclaringClass(metaClass);
     metaFunction->setImplementingClass(metaClass);
-    AbstractMetaClass::addFunction(metaClass, AbstractMetaFunctionCPtr(metaFunction));
+    AbstractMetaClass::addFunction(metaClass, metaFunction);
     metaClass->setHasNonPrivateConstructor(true);
     return true;
 }
 
-void AbstractMetaBuilderPrivate::fixArgumentNames(AbstractMetaFunction *func, const FunctionModificationList &mods)
+void AbstractMetaBuilderPrivate::fixArgumentNames(const AbstractMetaFunctionPtr &func,
+                                                  const FunctionModificationList &mods)
 {
     AbstractMetaArgumentList &arguments = func->arguments();
 
@@ -1920,7 +1914,7 @@ static AbstractMetaFunction::FunctionType functionTypeFromName(const QString &na
 
 // Apply the <array> modifications of the arguments
 static bool applyArrayArgumentModifications(const FunctionModificationList &functionMods,
-                                            AbstractMetaFunction *func,
+                                            const AbstractMetaFunctionPtr &func,
                                             QString *errorMessage)
 {
     for (const FunctionModification &mod : functionMods) {
@@ -1981,13 +1975,14 @@ void AbstractMetaBuilderPrivate::rejectFunction(const FunctionModelItem &functio
     m_rejectedFunctions.insert({reason, signatureWithType, sortKey, rejectReason});
 }
 
-AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const FunctionModelItem &functionItem,
-                                                                   const AbstractMetaClassPtr &currentClass)
+AbstractMetaFunctionPtr
+    AbstractMetaBuilderPrivate::traverseFunction(const FunctionModelItem &functionItem,
+                                                 const AbstractMetaClassPtr &currentClass)
 {
     const auto *tdb = TypeDatabase::instance();
 
     if (!functionItem->templateParameters().isEmpty())
-        return nullptr;
+        return {};
 
     if (functionItem->isDeleted()) {
         switch (functionItem->functionType()) {
@@ -2001,7 +1996,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
         default:
             break;
         }
-        return nullptr;
+        return {};
     }
     const QString &functionName = functionItem->name();
     const QString className = currentClass != nullptr ?
@@ -2011,7 +2006,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
         // Skip enum helpers generated by Q_ENUM
         if ((currentClass == nullptr || currentClass->isNamespace())
             && (functionName == u"qt_getEnumMetaObject" || functionName == u"qt_getEnumName")) {
-                return nullptr;
+                return {};
             }
 
         // Clang: Skip qt_metacast(), qt_metacall(), expanded from Q_OBJECT
@@ -2019,10 +2014,10 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
         if (currentClass != nullptr) {
             if (functionName == u"qt_check_for_QGADGET_macro"
                 || functionName.startsWith(u"qt_meta")) {
-                return nullptr;
+                return {};
             }
             if (functionName == u"metaObject" && className != u"QObject")
-                return nullptr;
+                return {};
         }
     } // PySide extensions
 
@@ -2030,7 +2025,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
     if (tdb->isFunctionRejected(className, functionName, &rejectReason)) {
         rejectFunction(functionItem, currentClass,
                        AbstractMetaBuilder::GenerationDisabled, rejectReason);
-        return nullptr;
+        return {};
     }
 
     const QString &signature = functionSignature(functionItem);
@@ -2041,22 +2036,22 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
             qCInfo(lcShiboken, "%s::%s was rejected by the type database (%s).",
                    qPrintable(className), qPrintable(signature), qPrintable(rejectReason));
         }
-        return nullptr;
+        return {};
     }
 
     if (functionItem->isFriend())
-        return nullptr;
+        return {};
 
     const auto cppAttributes = functionItem->attributes();
     const bool deprecated = cppAttributes.testFlag(FunctionAttribute::Deprecated);
     if (deprecated && m_skipDeprecated) {
         rejectFunction(functionItem, currentClass,
                        AbstractMetaBuilder::GenerationDisabled, u" is deprecated."_s);
-        return nullptr;
+        return {};
     }
 
     AbstractMetaFunction::Flags flags;
-    auto *metaFunction = new AbstractMetaFunction(functionName);
+    auto metaFunction = std::make_shared<AbstractMetaFunction>(functionName);
     metaFunction->setCppAttributes(cppAttributes);
     const QByteArray cSignature = signature.toUtf8();
     const QString unresolvedSignature =
@@ -2089,8 +2084,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
         if (tdb->isReturnTypeRejected(className, returnType.toString(), &rejectReason)) {
             rejectFunction(functionItem, currentClass,
                            AbstractMetaBuilder::GenerationDisabled, rejectReason);
-            delete metaFunction;
-            return nullptr;
+            return {};
         }
 
         TranslateTypeFlags flags;
@@ -2104,8 +2098,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
                       qPrintable(msgSkippingFunction(functionItem, signature, reason)));
             rejectFunction(functionItem, currentClass,
                            AbstractMetaBuilder::UnmatchedReturnType, reason);
-            delete metaFunction;
-            return nullptr;
+            return {};
         }
 
         metaFunction->setType(type.value());
@@ -2136,8 +2129,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
         if (tdb->isArgumentTypeRejected(className, arg->type().toString(), &rejectReason)) {
             rejectFunction(functionItem, currentClass,
                            AbstractMetaBuilder::GenerationDisabled, rejectReason);
-            delete metaFunction;
-            return nullptr;
+            return {};
         }
 
         TranslateTypeFlags flags;
@@ -2164,8 +2156,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
                       qPrintable(msgSkippingFunction(functionItem, signature, reason)));
             rejectFunction(functionItem, currentClass,
                            AbstractMetaBuilder::UnmatchedArgumentType, reason);
-            delete metaFunction;
-            return nullptr;
+            return {};
         }
 
         auto metaType = metaTypeO.value();
@@ -2188,8 +2179,8 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
     AbstractMetaArgumentList &metaArguments = metaFunction->arguments();
 
     const FunctionModificationList functionMods = currentClass
-        ? AbstractMetaFunction::findClassModifications(metaFunction, currentClass)
-        : AbstractMetaFunction::findGlobalModifications(metaFunction);
+        ? AbstractMetaFunction::findClassModifications(metaFunction.get(), currentClass)
+        : AbstractMetaFunction::findGlobalModifications(metaFunction.get());
 
     applyCachedFunctionModifications(metaFunction, functionMods);
 
@@ -2214,7 +2205,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio
             && metaFunction->argumentName(i + 1, false, currentClass).isEmpty()) {
             qCWarning(lcShiboken, "%s",
                       qPrintable(msgUnnamedArgumentDefaultExpression(currentClass, i + 1,
-                                                                     className, metaFunction)));
+                                                                     className, metaFunction.get())));
         }
 
     }
@@ -3315,7 +3306,7 @@ AbstractMetaFunctionPtr
     }
 
     QString errorMessage;
-    if (!applyArrayArgumentModifications(f->modifications(subclass), f.get(),
+    if (!applyArrayArgumentModifications(f->modifications(subclass), f,
                                          &errorMessage)) {
         qCWarning(lcShiboken, "While specializing %s (%s): %s",
                   qPrintable(subclass->name()), qPrintable(templateClass->name()),
index e65a4f176d601e57dbd8bd842cd557667e665769..4e337339eb29b2694beb14846878ccabb99b875a 100644 (file)
@@ -98,20 +98,21 @@ public:
                                                  const QSet<QString> &enumsDeclarations);
     void traverseEnums(const ScopeModelItem &item, const AbstractMetaClassPtr &parent,
                        const QStringList &enumsDeclarations);
-    AbstractMetaFunctionRawPtrList classFunctionList(const ScopeModelItem &scopeItem,
-                                                     AbstractMetaClass::Attributes *constructorAttributes,
-                                                     const AbstractMetaClassPtr &currentClass);
+    AbstractMetaFunctionList classFunctionList(const ScopeModelItem &scopeItem,
+                                               AbstractMetaClass::Attributes *constructorAttributes,
+                                               const AbstractMetaClassPtr &currentClass);
     void traverseFunctions(const ScopeModelItem& item,
                            const AbstractMetaClassPtr &parent);
-    static void applyFunctionModifications(AbstractMetaFunction *func);
+    static void applyFunctionModifications(const AbstractMetaFunctionPtr &func);
     void traverseFields(const ScopeModelItem &item, const AbstractMetaClassPtr &parent);
     bool traverseStreamOperator(const FunctionModelItem &functionItem,
                                 const AbstractMetaClassPtr &currentClass);
     void traverseOperatorFunction(const FunctionModelItem &item,
                                   const AbstractMetaClassPtr &currentClass);
-    AbstractMetaFunction *traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc,
-                                                      const AbstractMetaClassPtr &metaClass,
-                                                      QString *errorMessage);
+    AbstractMetaFunctionPtr
+        traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc,
+                                    const AbstractMetaClassPtr &metaClass,
+                                    QString *errorMessage);
     bool traverseAddedGlobalFunction(const AddedFunctionPtr &addedFunc,
                                      QString *errorMessage);
     bool traverseAddedMemberFunction(const AddedFunctionPtr &addedFunc,
@@ -121,8 +122,9 @@ public:
                         const AbstractMetaClassPtr &currentClass,
                         AbstractMetaBuilder::RejectReason reason,
                         const QString &rejectReason);
-        AbstractMetaFunction *traverseFunction(const FunctionModelItem &function,
-                                           const AbstractMetaClassPtr &currentClass);
+    AbstractMetaFunctionPtr
+        traverseFunction(const FunctionModelItem &function,
+                         const AbstractMetaClassPtr &currentClass);
     std::optional<AbstractMetaField> traverseField(const VariableModelItem &field,
                                                    const AbstractMetaClassCPtr &cls);
     void checkFunctionModifications() const;
@@ -142,7 +144,7 @@ public:
      *   said class.
      *   \param metaFunction conversion operator function to be fixed.
      */
-    static void fixReturnTypeOfConversionOperator(AbstractMetaFunction *metaFunction);
+    static void fixReturnTypeOfConversionOperator(const AbstractMetaFunctionPtr &metaFunction);
 
     void parseQ_Properties(const AbstractMetaClassPtr &metaClass,
                            const QStringList &declarations);
@@ -212,7 +214,8 @@ public:
 
     void sortLists();
     void setInclude(const TypeEntryPtr &te, const QString &path) const;
-    static void fixArgumentNames(AbstractMetaFunction *func, const FunctionModificationList &mods);
+    static void fixArgumentNames(const AbstractMetaFunctionPtr &func,
+                                 const FunctionModificationList &mods);
 
     void fillAddedFunctions(const AbstractMetaClassPtr &metaClass);
     AbstractMetaClassCPtr resolveTypeSystemTypeDef(const AbstractMetaType &t) const;
index 11a02f154f4d4d8b80c55d81be652afd3add0644..310d611f3d95bca5d0beab36443daa46281f8724 100644 (file)
@@ -542,7 +542,8 @@ QString AbstractMetaFunctionPrivate::signature() const
                 m_cachedSignature += u", "_s;
             m_cachedSignature += t.cppSignature();
             // We need to have the argument names in the qdoc files
-            m_cachedSignature += u' ';
+            if (!m_cachedSignature.endsWith(u'*') && !m_cachedSignature.endsWith(u'&'))
+                m_cachedSignature += u' ';
             m_cachedSignature += a.name();
         }
         m_cachedSignature += u')';
index 802f549cf1448aed9c60fe8660bbb15bd201051e..27321ca2d6f4eb8366063ce0c0a9f98fe467250b 100644 (file)
@@ -30,6 +30,7 @@ using AbstractMetaEnumValueList = QList<AbstractMetaEnumValue>;
 using AbstractMetaFieldList = QList<AbstractMetaField>;
 using AbstractMetaFunctionRawPtrList = QList<AbstractMetaFunction *>;
 using AbstractMetaFunctionCList = QList<AbstractMetaFunctionCPtr>;
+using AbstractMetaFunctionList = QList<AbstractMetaFunctionPtr>;
 using AbstractMetaTypeList = QList<AbstractMetaType>;
 using UsingMembers = QList<UsingMember>;
 
index e1e6ab7f00713588603ad75c4989ee8c07a2caf1..2dac4db2ef7fe48ce7e4c0f3571224cecf205f89 100644 (file)
@@ -4,6 +4,7 @@
 #ifndef ANYSTRINGVIEW_STREAM_H
 #define ANYSTRINGVIEW_STREAM_H
 
+#include <QtCore/QtTypes>
 #include <QtCore/QtClassHelperMacros>
 
 QT_FORWARD_DECLARE_CLASS(QAnyStringView)
index ea37c62557e44a1ca622e92e9da24a55fbe0abb9..635f2dcc32e06fde8570101c46d6a7b87924df71 100644 (file)
@@ -424,7 +424,7 @@ typedef Vector<int> IntVector;
 
     const auto method = vector->findFunction("method");
     QVERIFY(method);
-    QCOMPARE(method->signature(), u"method(const Vector<int> & vector)");
+    QCOMPARE(method->signature(), "method(const Vector<int> &vector)"_L1);
 
     const auto otherMethod = vector->findFunction("otherMethod");
     QVERIFY(otherMethod);
index 749c4baa33bd7a5a83a500bcc45358d50dc65104..61fd2241877a5afb8388909e274b3bf669d1d240 100644 (file)
@@ -88,7 +88,8 @@ static const PythonTypes &builtinPythonTypes()
         {u"PyObject"_s, u"true"_s, TypeSystem::CPythonType::Other},
         // shiboken-specific
         {u"PyPathLike"_s, u"Shiboken::String::checkPath"_s, TypeSystem::CPythonType::Other},
-        {u"PySequence"_s, u"Shiboken::String::checkIterable"_s, TypeSystem::CPythonType::Other},
+        {u"PySequence"_s, u"Shiboken::String::checkIterableArgument"_s,
+         TypeSystem::CPythonType::Other},
         {u"PyUnicode"_s, u"PyUnicode_Check"_s, TypeSystem::CPythonType::String},
         {u"PyTypeObject"_s, u"PyType_Check"_s, TypeSystem::CPythonType::Other},
         {u"str"_s, u"Shiboken::String::check"_s, TypeSystem::CPythonType::String},
index 8bc0661023f984f271c9cdfbbb2d97518a426a7c..27ee33305454ee712197eac4c0c7eca51f59b763 100644 (file)
@@ -202,12 +202,6 @@ macro(get_python_extension_suffix)
     # Python_SOABI is only set by CMake 3.17+
     # TODO: Lower this to CMake 3.16 if possible.
     if(SHIBOKEN_IS_CROSS_BUILD)
-        # For android platform armv7a FindPython module return Python_SOABI as empty because
-        # it is unable to set Python_CONFIG i.e. find `python3-config` script
-        # This workaround sets the Python_SOABI manually for this platform.
-        if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7-a")
-            set(Python_SOABI "cpython-311}")
-        endif()
         if(NOT Python_SOABI)
             message(FATAL_ERROR "Python_SOABI variable is empty.")
         endif()
@@ -320,6 +314,17 @@ macro(shiboken_find_required_python)
             "${_shiboken_backup_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}")
         set(CMAKE_FIND_ROOT_PATH
             "${_shiboken_backup_CMAKE_FIND_ROOT_PATH}")
+
+        # For Android platform sometimes the FindPython module returns Python_SOABI as empty in
+        # certain scenarios eg: armv7a target, macOS host etc. This is because
+        # it is unable to set Python_CONFIG i.e. `python3-config` script
+        # This workaround sets the Python_SOABI manually for this Android platform.
+        # This needs to be updated manually if the Python version for Android cross compilation
+        # changes.
+        # TODO: Find a better way to set Python_SOABI for Android platform
+        if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND NOT Python_SOABI)
+            set(Python_SOABI "cpython-311")
+        endif()
     else()
         find_package(
             Python
@@ -877,7 +882,7 @@ function(shiboken_get_debug_level out_var)
     set(debug_level "")
     if(SHIBOKEN_DEBUG_LEVEL)
         set(debug_level "--debug-level=${SHIBOKEN_DEBUG_LEVEL}")
-    elseif(DEFINED $ENV{SHIBOKEN_DEBUG_LEVEL})
+    elseif(DEFINED ENV{SHIBOKEN_DEBUG_LEVEL})
         set(debug_level "--debug-level=$ENV{SHIBOKEN_DEBUG_LEVEL}")
     endif()
     set(${out_var} "${debug_level}" PARENT_SCOPE)
index 26f929801630c88661e2a3dd01d90fc54521473c..37a4a661b794c8c9e28fc2eca9636bc0075f1cce 100644 (file)
@@ -38,12 +38,11 @@ Modifying types
 .. toctree::
    :maxdepth: 1
 
-   typesystem_arguments.rst
+   Function argument modifications <typesystem_arguments.rst>
    typesystem_codeinjection.rst
    typesystem_converters.rst
    typesystem_containers.rst
    typesystem_templates.rst
-   typesystem_modify_function.rst
    typesystem_manipulating_objects.rst
    typesystem_conversionrule.rst
    typesystem_documentation.rst
index d950b6c3266347e5b37b42bd623aa6322976ffe9..c3ad1c7077befafcbcd6a57af4641819e78cb374 100644 (file)
@@ -1,56 +1,45 @@
-.. _modifying-arguments:
+.. _modify-argument:
 
-Modifying Arguments
--------------------
+modify-argument
+---------------
 
-.. _conversionrule-on-arguments:
-
-conversion-rule
-^^^^^^^^^^^^^^^
-
-The ``conversion-rule`` node allows you to write customized code to convert
-the given argument between the target language and C++.
-It is then a child of the :ref:`modify-argument` node:
+Function argument modifications consist of a list of ``modify-argument`` nodes
+contained in :ref:`modify-function`, :ref:`add-function` or
+:ref:`declare-function` nodes. Nested :ref:`remove-argument`,
+:ref:`replace-default-expression`, :ref:`remove-default-expression`,
+:ref:`replace-type`, :ref:`reference-count` and :ref:`define-ownership`
+nodes specify the details of the modification.
 
 .. code-block:: xml
 
-    <modify-argument index="2">
-    <!-- for the second argument of the function -->
-    <conversion-rule class="target | native">
-        // the code
-    </conversion-rule>
-    </modify-argument>
+     <modify-function>
+         <modify-argument index="return | this | 1 ..." rename="..."
+          invalidate-after-use = "true | false" pyi-type="...">
+             // modifications
+         </modify-argument>
+     </modify-function>
 
-The ``class`` attribute accepts one of the following values to define the
-conversion direction to be either ``target-to-native`` or ``native-to-target``:
+Set the ``index`` attribute to "1" for the first argument, "2" for the second
+one and so on. Alternatively, set it to "return" or "this" if you want to
+modify the function's return value or the object the function is called upon,
+respectively.
 
-* ``native``: Defines the conversion direction to be ``target-to-native``.
-              It is similar to the existing ``<target-to-native>`` element.
-              See :ref:`Conversion Rule Tag <conversion-rule-tag>` for more information.
-
-* ``target``: Defines the conversion direction to be ``native-to-target``.
-              It is similar to the existing ``<native-to-target>`` element.
-              See :ref:`Conversion Rule Tag <conversion-rule-tag>` for more information.
-
-This node is typically used in combination with the :ref:`replace-type` and
-:ref:`remove-argument` nodes. The given code is used instead of the generator's
-conversion code.
+The optional ``rename`` attribute is used to rename a argument and use this
+new name in the generated code. This attribute can be used to enable the usage
+of ``keyword arguments``.
 
-Writing %N in the code (where N is a number), will insert the name of the
-nth argument. Alternatively, %in and %out which will be replaced with the
-name of the conversion's input and output variable, respectively. Note the
-output variable must be declared explicitly, for example:
-
-.. code-block:: xml
-
-    <conversion-rule class="native">
-    bool %out = (bool) %in;
-    </conversion-rule>
+The optional ``pyi-type`` attribute specifies the type to appear in the
+signature strings and  ``.pyi`` files. The type string is determined by
+checking this attribute value, the :ref:`replace-type` modification and
+the C++ type. The attribute can be used for example to enclose
+a pointer return value within ``Optional[]`` to indicate that ``None``
+can occur.
 
-.. note::
+For the optional ``invalidate-after-use`` attribute,
+see :ref:`invalidationafteruse` .
 
-    You can also use the ``conversion-rule`` node to specify
-    :ref:`a conversion code which will be used instead of the generator's conversion code everywhere for a given type <conversion-rule-tag>`.
+Naming, type, default value modifications
++++++++++++++++++++++++++++++++++++++++++
 
 .. _remove-argument:
 
@@ -130,6 +119,9 @@ If the new type is a class, the ``modified-type`` attribute must be set to
 the fully qualified name (including name of the package as well as the class
 name).
 
+Ownership/Reference modifications
++++++++++++++++++++++++++++++++++
+
 .. _define-ownership:
 
 define-ownership
@@ -138,25 +130,26 @@ define-ownership
 The ``define-ownership`` tag indicates that the function changes the ownership
 rules of the argument object, and it is a child of the
 :ref:`modify-argument` node.
+
+.. code-block:: xml
+
+    <modify-argument>
+          <define-ownership class="target | native"
+                            owner="target | c++ | default" />
+    </modify-argument>
+
 The ``class`` attribute specifies the class of
 function where to inject the ownership altering code
 (see :ref:`codegenerationterminology`). The ``owner`` attribute
 specifies the new ownership of the object. It accepts the following values:
 
 * target: the target language will assume full ownership of the object.
-          The native resources will be deleted when the target language
-          object is finalized.
+  The native resources will be deleted when the target language
+  object is finalized.
 * c++: The native code assumes full ownership of the object. The target
-       language object will not be garbage collected.
+  language object will not be garbage collected.
 * default: The object will get default ownership, depending on how it
-           was created.
-
-.. code-block:: xml
-
-    <modify-argument>
-          <define-ownership class="target | native"
-                            owner="target | c++ | default" />
-    </modify-argument>
+  was created.
 
 .. _reference-count:
 
@@ -166,47 +159,40 @@ reference-count
 The ``reference-count`` tag dictates how an argument should be handled by the
 target language reference counting system (if there is any), it also indicates
 the kind of relationship the class owning the function being modified has with
-the argument. It is a child of the :ref:`modify-argument` node.
-For instance, in a model/view relation a view receiving a model
-as argument for a **setModel** method should increment the model's reference
-counting, since the model should be kept alive as much as the view lives.
-Remember that out hypothetical view could not become parent of the model,
-since the said model could be used by other views as well.
-The ``action`` attribute specifies what should be done to the argument
-reference counting when the modified method is called. It accepts the
-following values:
-
-* add: increments the argument reference counter.
-* add-all: increments the reference counter for each item in a collection.
-* remove: decrements the argument reference counter.
-* set: will assign the argument to the variable containing the reference.
-* ignore: does nothing with the argument reference counter
-          (sounds worthless, but could be used in situations
-           where the reference counter increase is mandatory
-           by default).
+the argument (represented as lists of referred-to objects stored in the
+owner class). It is a child of the :ref:`modify-argument` node.
 
 .. code-block:: xml
 
     <modify-argument>
-          <reference-count action="add|add-all|remove|set|ignore" variable-name="..." />
+          <reference-count action="add|remove|set|ignore" variable-name="..." />
     </modify-argument>
 
+The ``action`` attribute specifies what should be done to the argument
+reference counting when the modified method is called. It accepts the
+following values:
 
-The variable-name attribute specifies the name used for the variable that
-holds the reference(s).
-
-.. _replace-value:
-
-replace-value
-^^^^^^^^^^^^^
-
-The ``replace-value`` attribute lets you replace the return statement of a
-function with a fixed string. This attribute can only be used for the
-argument at ``index`` 0, which is always the function's return value.
+* add: Adds the argument to the list of previous argument values stored
+  under this ``variable-name`` or function signature and increments
+  the argument reference counter.
+* remove: Decrements the argument reference counter and removes it from
+  the list of  argument values stored under this ``variable-name``
+  or function signature.
+* set: Decreases the reference count of the previously stored argument values
+  under this ``variable-name`` or function signature and removes them.
+  Stores the argument and increments the argument reference counter.
+* ignore: does nothing with the argument reference counter
+  (sounds worthless, but could be used in situations
+  where the reference counter increase is mandatory by default).
 
-.. code-block:: xml
+The ``variable-name`` attribute specifies the name used for the variable that
+holds the reference(s). It defaults to the function signature.
 
-     <modify-argument index="0" replace-value="this"/>
+For instance, in a model/view relation, a view receiving a model
+as argument for a **setModel()** method should increment the model's reference
+counting, since the model should be kept alive as long as the view lives.
+Remember that our hypothetical view cannot become a :ref:`parent` of the
+model, since the said model could be used by other views as well.
 
 .. _parent:
 
@@ -227,3 +213,68 @@ It is a child of the :ref:`modify-argument` node.
 In the ``index`` argument you must specify the parent argument. The action
 *add* creates a parent link between objects, while *remove* will undo the
 parentage relationship.
+
+Other modifications
++++++++++++++++++++
+
+.. _conversionrule-on-arguments:
+
+conversion-rule
+^^^^^^^^^^^^^^^
+
+The ``conversion-rule`` node allows you to write customized code to convert
+the given argument between the target language and C++.
+It is then a child of the :ref:`modify-argument` node:
+
+.. code-block:: xml
+
+    <modify-argument index="2">
+    <!-- for the second argument of the function -->
+    <conversion-rule class="target | native">
+        // the code
+    </conversion-rule>
+    </modify-argument>
+
+The ``class`` attribute accepts one of the following values to define the
+conversion direction to be either ``target-to-native`` or ``native-to-target``:
+
+* ``native``: Defines the conversion direction to be ``target-to-native``.
+              It is similar to the existing ``<target-to-native>`` element.
+              See :ref:`Conversion Rule Tag <conversion-rule-tag>` for more information.
+
+* ``target``: Defines the conversion direction to be ``native-to-target``.
+              It is similar to the existing ``<native-to-target>`` element.
+              See :ref:`Conversion Rule Tag <conversion-rule-tag>` for more information.
+
+This node is typically used in combination with the :ref:`replace-type` and
+:ref:`remove-argument` nodes. The given code is used instead of the generator's
+conversion code.
+
+Writing %N in the code (where N is a number), will insert the name of the
+nth argument. Alternatively, %in and %out which will be replaced with the
+name of the conversion's input and output variable, respectively. Note the
+output variable must be declared explicitly, for example:
+
+.. code-block:: xml
+
+    <conversion-rule class="native">
+    bool %out = (bool) %in;
+    </conversion-rule>
+
+.. note::
+
+    You can also use the ``conversion-rule`` node to specify
+    :ref:`a conversion code which will be used instead of the generator's conversion code everywhere for a given type <conversion-rule-tag>`.
+
+.. _replace-value:
+
+replace-value
+^^^^^^^^^^^^^
+
+The ``replace-value`` attribute lets you replace the return statement of a
+function with a fixed string. This attribute can only be used for the
+argument at ``index`` 0, which is always the function's return value.
+
+.. code-block:: xml
+
+     <modify-argument index="0" replace-value="this"/>
index e024cdf0002fae3d6446b522f41673898cca159f..fe1e41e7417a2d75063ac2a92294a46361d2a182 100644 (file)
@@ -154,8 +154,8 @@ modify-function
 The ``modify-function`` node allows you to modify a given C++ function when
 mapping it onto the target language, and it is a child of a :ref:`function`,
 :ref:`namespace`, :ref:`object-type` or a :ref:`value-type` node.
-Use the :ref:`modify-argument` node to specify which argument the
-modification affects.
+Nested :ref:`modify-argument` nodes can used to modify arguments
+or return values.
 
 .. code-block:: xml
 
diff --git a/sources/shiboken6/doc/typesystem_modify_function.rst b/sources/shiboken6/doc/typesystem_modify_function.rst
deleted file mode 100644 (file)
index 54ac641..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-.. _modifying-functions:
-
-Modifying Functions
--------------------
-
-.. _modify-argument:
-
-modify-argument
-^^^^^^^^^^^^^^^
-
-Function modifications consist of a list of ``modify-argument`` nodes
-contained in :ref:`modify-function`, :ref:`add-function` or
-:ref:`declare-function` nodes. Use the :ref:`remove-argument`,
-:ref:`replace-default-expression`, :ref:`remove-default-expression`,
-:ref:`replace-type`, :ref:`reference-count` and :ref:`define-ownership`
-nodes to specify the details of the modification.
-
-.. code-block:: xml
-
-     <modify-function>
-         <modify-argument index="return | this | 1 ..." rename="..."
-          invalidate-after-use = "true | false" pyi-type="...">
-             // modifications
-         </modify-argument>
-     </modify-function>
-
-Set the ``index`` attribute to "1" for the first argument, "2" for the second
-one and so on. Alternatively, set it to "return" or "this" if you want to
-modify the function's return value or the object the function is called upon,
-respectively.
-
-The optional ``rename`` attribute is used to rename a argument and use this
-new name in the generated code. This attribute can be used to enable the usage
-of ``keyword arguments``.
-
-The optional ``pyi-type`` attribute specifies the type to appear in the
-signature strings and  ``.pyi`` files. The type string is determined by
-checking this attribute value, the :ref:`replace-type` modification and
-the C++ type. The attribute can be used for example to enclose
-a pointer return value within ``Optional[]`` to indicate that ``None``
-can occur.
-
-For the optional ``invalidate-after-use`` attribute,
-see :ref:`invalidationafteruse` .
index a0132653018ad09dd381680bfbd1011728eb503f..2f14f2f52b742998aa6027f9413296a50d0c6ae9 100644 (file)
@@ -240,6 +240,7 @@ QString Generator::getFileNameBaseForSmartPointer(const AbstractMetaType &smartP
 GeneratorContext Generator::contextForClass(const AbstractMetaClassCPtr &c) const
 {
     GeneratorContext result;
+    result.m_type = GeneratorContext::Class;
     result.m_metaClass = c;
     return result;
 }
index b50c2effb8dcf97183a58a87a9c7820ab1429ecc..4021704251e746e2c51c58d83c81cd14e0e11d01 100644 (file)
@@ -16,6 +16,7 @@ QString GeneratorContext::wrapperName() const
 
 QString GeneratorContext::effectiveClassName() const
 {
+    Q_ASSERT(hasClass());
     if (m_type == SmartPointer)
         return m_preciseClassType.cppSignature();
     return m_type == WrappedClass ? m_wrappername : m_metaClass->qualifiedCppName();
index 2e58d43461408663dde079d8efd2d0f3815be232..b604d5f1a2576c0d06b06918d2a4b2102fe0bc54 100644 (file)
@@ -27,16 +27,34 @@ class GeneratorContext {
     friend class ShibokenGenerator;
     friend class Generator;
 public:
-    enum Type { Class, WrappedClass, SmartPointer };
+    enum Type { Class, WrappedClass, SmartPointer,
+                GlobalFunction // No class contained
+              };
 
     GeneratorContext() = default;
 
-    AbstractMetaClassCPtr metaClass() const { return m_metaClass; }
-    const AbstractMetaType &preciseType() const { return m_preciseClassType; }
-    AbstractMetaClassCPtr pointeeClass() const { return m_pointeeClass; }
+    const AbstractMetaClassCPtr &metaClass() const
+    {
+        Q_ASSERT(hasClass());
+        return m_metaClass;
+    }
+
+    const AbstractMetaType &preciseType() const
+    {
+        Q_ASSERT(forSmartPointer());
+        return m_preciseClassType;
+    }
+
+    AbstractMetaClassCPtr pointeeClass() const
+    {
+
+        Q_ASSERT(forSmartPointer());
+        return m_pointeeClass;
+    }
 
     bool forSmartPointer() const { return m_type == SmartPointer; }
     bool useWrapper() const { return m_type ==  WrappedClass; }
+    bool hasClass() const { return m_type != GlobalFunction; }
 
     QString wrapperName() const;
     /// Returns the wrapper name in case of useWrapper(), the qualified class
@@ -48,7 +66,7 @@ private:
     AbstractMetaClassCPtr m_pointeeClass;
     AbstractMetaType m_preciseClassType;
     QString m_wrappername;
-    Type m_type = Class;
+    Type m_type = GlobalFunction;
 };
 
 QDebug operator<<(QDebug debug, const GeneratorContext &c);
index 9fb5b9bf61a5f1ab2cd41e27c9f0df9f26a57de7..94e61e7064175042b0c159710b7f710d0954d24c 100644 (file)
@@ -1048,7 +1048,7 @@ bool QtDocGenerator::writeInheritanceFile()
 {
     QFile inheritanceFile(m_options.inheritanceFile);
     if (!inheritanceFile.open(QIODevice::WriteOnly | QIODevice::Text))
-        throw Exception(msgCannotOpenForWriting(m_options.inheritanceFile));
+        throw Exception(msgCannotOpenForWriting(inheritanceFile));
 
     QJsonObject dict;
     for (const auto &c : api().classes()) {
index a7013aded14fc9a63966e73292ea19cfd5d47b3b..5caabcc3ef4a83a54c35308ca6d7e9c38968852a 100644 (file)
@@ -61,6 +61,7 @@ static const char shibokenErrorsOccurred[] = "Shiboken::Errors::occurred() != nu
 static constexpr auto virtualMethodStaticReturnVar = "result"_L1;
 
 static constexpr auto sbkObjectTypeF = "SbkObject_TypeF()"_L1;
+static constexpr auto enumConverterPythonType = "Enum"_L1;
 static const char initInheritanceFunction[] = "initInheritance";
 
 static QString mangleName(QString name)
@@ -1576,17 +1577,17 @@ void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMeta
     c << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) = value;\n";
 
     ConfigurableScope configScope(s, enumType);
-    writePythonToCppFunction(s, c.toString(), typeName, typeName);
+    writePythonToCppFunction(s, c.toString(), enumConverterPythonType, typeName);
 
     QString pyTypeCheck = u"PyObject_TypeCheck(pyIn, "_s + enumPythonType + u')';
-    writeIsPythonConvertibleToCppFunction(s, typeName, typeName, pyTypeCheck);
+    writeIsPythonConvertibleToCppFunction(s, enumConverterPythonType, typeName, pyTypeCheck);
 
     c.clear();
 
     c << "const int castCppIn = int(*reinterpret_cast<const "
         << cppTypeName << " *>(cppIn));\n" << "return "
         << "Shiboken::Enum::newItem(" << enumPythonType << ", castCppIn);\n";
-    writeCppToPythonFunction(s, c.toString(), typeName, typeName);
+    writeCppToPythonFunction(s, c.toString(), typeName, enumConverterPythonType);
     s << '\n';
 }
 
@@ -1971,14 +1972,14 @@ void CppGenerator::writeMethodWrapperPreamble(TextStream &s,
                                               ErrorReturn errorReturn)
 {
     const auto rfunc = overloadData.referenceFunction();
-    const auto ownerClass = rfunc->targetLangOwner();
-    Q_ASSERT(ownerClass == context.metaClass());
     int minArgs = overloadData.minArgs();
     int maxArgs = overloadData.maxArgs();
     bool initPythonArguments;
 
     // If method is a constructor...
     if (rfunc->isConstructor()) {
+        const auto ownerClass = rfunc->targetLangOwner();
+        Q_ASSERT(ownerClass == context.metaClass());
         // Check if the right constructor was called.
         if (!ownerClass->hasPrivateDestructor()) {
             s << "if (Shiboken::Object::isUserType(self) && "
@@ -2039,7 +2040,7 @@ void CppGenerator::writeMethodWrapperPreamble(TextStream &s,
             && !overloadData.pythonFunctionWrapperUsesListOfArguments()) {
             s << "(" << PYTHON_ARG << " == 0 ? 0 : 1);\n";
         } else {
-            writeArgumentsInitializer(s, overloadData, errorReturn);
+            writeArgumentsInitializer(s, overloadData, context, errorReturn);
         }
     }
 }
@@ -2110,7 +2111,7 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov
     s << '\n';
 
     if (overloadData.maxArgs() > 0)
-        writeOverloadedFunctionDecisor(s, overloadData, errorReturn);
+        writeOverloadedFunctionDecisor(s, overloadData, classContext, errorReturn);
 
     // Handles Python Multiple Inheritance
     QString pre = needsMetaObject ? u"bool usesPyMI = "_s : u""_s;
@@ -2131,8 +2132,8 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov
         << "}\n";
     if (overloadData.maxArgs() > 0)
         s << "if (cptr == nullptr)\n" << indent
-            << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n\n"
-            << outdent;
+            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+            << ";\n\n" << outdent;
 
     s << "Shiboken::Object::setValidCpp(sbkSelf, true);\n";
     // If the created C++ object has a C++ wrapper the ownership is assigned to Python
@@ -2155,8 +2156,8 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov
             << "metaObject = cptr->metaObject(); // <- init python qt properties\n"
             << "if (!errInfo.isNull() && PyDict_Check(errInfo.object())) {\n" << indent
                 << "if (!PySide::fillQtProperties(self, metaObject, errInfo, usesPyMI))\n" << indent
-                    << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"
-                    << outdent << outdent
+                    << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+                    << ";\n" << outdent << outdent
             << "};\n";
     }
 
@@ -2258,12 +2259,12 @@ void CppGenerator::writeMethodWrapper(TextStream &s, const OverloadData &overloa
             << "// Do not enter here if other object has implemented a reverse operator.\n"
             << "if (" << PYTHON_RETURN_VAR << " == nullptr) {\n" << indent;
         if (maxArgs > 0)
-            writeOverloadedFunctionDecisor(s, overloadData, ErrorReturn::Default);
+            writeOverloadedFunctionDecisor(s, overloadData, classContext, ErrorReturn::Default);
         writeFunctionCalls(s, overloadData, classContext, ErrorReturn::Default);
         s  << outdent << '\n' << "} // End of \"if (!" << PYTHON_RETURN_VAR << ")\"\n";
     } else { // binary shift operator
         if (maxArgs > 0)
-            writeOverloadedFunctionDecisor(s, overloadData, ErrorReturn::Default);
+            writeOverloadedFunctionDecisor(s, overloadData, classContext, ErrorReturn::Default);
         writeFunctionCalls(s, overloadData, classContext, ErrorReturn::Default);
     }
 
@@ -2286,6 +2287,7 @@ void CppGenerator::writeMethodWrapper(TextStream &s, const OverloadData &overloa
 }
 
 void CppGenerator::writeArgumentsInitializer(TextStream &s, const OverloadData &overloadData,
+                                             const GeneratorContext &classContext,
                                              ErrorReturn errorReturn)
 {
     const auto rfunc = overloadData.referenceFunction();
@@ -2326,7 +2328,7 @@ void CppGenerator::writeArgumentsInitializer(TextStream &s, const OverloadData &
         s << "errInfo.reset(Shiboken::checkInvalidArgumentCount(numArgs, "
             <<  minArgs << ", " << maxArgs << "));\n"
             << "if (!errInfo.isNull())\n" << indent
-            << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"
+            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn) << ";\n"
             << outdent;
     }
 
@@ -2339,7 +2341,7 @@ void CppGenerator::writeArgumentsInitializer(TextStream &s, const OverloadData &
             s << "numArgs == " << invalidArgsLength.at(i);
         }
         s << ")\n" << indent
-            << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"
+            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn) << ";\n"
             << outdent;
     }
     s  << '\n';
@@ -2459,8 +2461,10 @@ void CppGenerator::writeCppSelfDefinition(TextStream &s,
 }
 
 QString CppGenerator::returnErrorWrongArguments(const OverloadData &overloadData,
-                                                       ErrorReturn errorReturn)
+                                                const GeneratorContext &context,
+                                                ErrorReturn errorReturn)
 {
+    Q_UNUSED(context);
     const auto rfunc = overloadData.referenceFunction();
     QString argsVar = overloadData.pythonFunctionWrapperUsesListOfArguments()
         ? u"args"_s : PYTHON_ARG;
@@ -2864,6 +2868,7 @@ void CppGenerator::writeNoneReturn(TextStream &s, const AbstractMetaFunctionCPtr
 
 void CppGenerator::writeOverloadedFunctionDecisor(TextStream &s,
                                                   const OverloadData &overloadData,
+                                                  const GeneratorContext &classContext,
                                                   ErrorReturn errorReturn) const
 {
     s << "// Overloaded function decisor\n";
@@ -2892,8 +2897,8 @@ void CppGenerator::writeOverloadedFunctionDecisor(TextStream &s,
 
     s << "// Function signature not found.\n"
         << "if (overloadId == -1)\n" << indent
-            << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n\n"
-            << outdent;
+            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+            << ";\n\n" << outdent;
 }
 
 void CppGenerator::writeOverloadedFunctionDecisorEngine(TextStream &s,
@@ -3109,7 +3114,7 @@ void CppGenerator::writeSingleFunctionCall(TextStream &s,
     const bool usePyArgs = overloadData.pythonFunctionWrapperUsesListOfArguments();
 
     // Handle named arguments.
-    writeNamedArgumentResolution(s, func, usePyArgs, overloadData, errorReturn);
+    writeNamedArgumentResolution(s, func, usePyArgs, overloadData, context, errorReturn);
 
     bool injectCodeCallsFunc = injectedCodeCallsCppFunction(context, func);
     bool mayHaveUnunsedArguments = !func->isUserAdded() && func->hasInjectedCode() && injectCodeCallsFunc;
@@ -3505,6 +3510,7 @@ void CppGenerator::writeNamedArgumentResolution(TextStream &s,
                                                 const AbstractMetaFunctionCPtr &func,
                                                 bool usePyArgs,
                                                 const OverloadData &overloadData,
+                                                const GeneratorContext &classContext,
                                                 ErrorReturn errorReturn)
 {
     const AbstractMetaArgumentList &args = OverloadData::getArgumentsWithDefaultValues(func);
@@ -3517,8 +3523,8 @@ void CppGenerator::writeNamedArgumentResolution(TextStream &s,
             s << "if (kwds != nullptr && PyDict_Size(kwds) > 0) {\n" << indent
                 << "errInfo.reset(kwds);\n"
                 << "Py_INCREF(errInfo.object());\n"
-                << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"
-                << outdent << "}\n";
+                << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+                << ";\n" << outdent << "}\n";
         }
         return;
     }
@@ -3541,14 +3547,14 @@ void CppGenerator::writeNamedArgumentResolution(TextStream &s,
             << "if (value != nullptr && " << pyArgName << " != nullptr ) {\n"
             << indent << "errInfo.reset(" << pyKeyName << ");\n"
             << "Py_INCREF(errInfo.object());\n"
-            << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"
-            << outdent << "}\nif (value != nullptr) {\n" << indent
+            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+            << ";\n" << outdent << "}\nif (value != nullptr) {\n" << indent
             << pyArgName << " = value;\nif (!";
         const auto &type = arg.modifiedType();
         writeTypeCheck(s, type, pyArgName, isNumber(type.typeEntry()), {});
         s << ")\n" << indent
-            << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"
-            << outdent << outdent
+            << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+            << ";\n" << outdent << outdent
             << "}\nPyDict_DelItem(kwds_dup, " << pyKeyName << ");\n"
             << outdent << "}\n";
     }
@@ -3558,10 +3564,12 @@ void CppGenerator::writeNamedArgumentResolution(TextStream &s,
     // until extra keyword signals and properties are handled.
     s << "if (PyDict_Size(kwds_dup) > 0) {\n" << indent
         << "errInfo.reset(kwds_dup.release());\n";
-    if (!(func->isConstructor() && isQObject(func->ownerClass())))
-        s << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n";
-    else
+    if (!(func->isConstructor() && isQObject(func->ownerClass()))) {
+        s << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+            << ";\n";
+    } else {
         s << "// fall through to handle extra keyword signals and properties\n";
+    }
     s << outdent << "}\n"
         << outdent << "}\n";
 }
@@ -4190,10 +4198,10 @@ void CppGenerator::writeEnumConverterInitialization(TextStream &s, const Abstrac
     const QString typeName = fixedCppTypeName(enumType);
     s << "SbkConverter *converter = Shiboken::Conversions::createConverter("
         << enumPythonVar << ',' << '\n' << indent
-        << cppToPythonFunctionName(typeName, typeName) << ");\n" << outdent;
+        << cppToPythonFunctionName(typeName, enumConverterPythonType) << ");\n" << outdent;
 
-    const QString toCpp = pythonToCppFunctionName(typeName, typeName);
-    const QString isConv = convertibleToCppFunctionName(typeName, typeName);
+    QString toCpp = pythonToCppFunctionName(enumConverterPythonType, typeName);
+    const QString isConv = convertibleToCppFunctionName(enumConverterPythonType, typeName);
     writeAddPythonToCppConversion(s, u"converter"_s, toCpp, isConv);
     s << "Shiboken::Enum::setTypeConverter(" << enumPythonVar
         << ", converter);\n";
@@ -4257,10 +4265,24 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s,
     return converter;
 }
 
+QString CppGenerator::typeInitStructHelper(const TypeEntryCPtr &te, const QString &varName)
+{
+    return cppApiVariableName(te->targetLangPackage()) + u'[' + varName + u']';
+}
+
+QString CppGenerator::typeInitStruct(const GeneratorContext &context)
+{
+    Q_ASSERT(context.hasClass());
+    if (context.forSmartPointer()) {
+        auto te = context.preciseType().typeEntry();
+        return typeInitStructHelper(te, getTypeIndexVariableName(context.preciseType()));
+    }
+    return typeInitStruct(context.metaClass()->typeEntry());
+}
+
 QString CppGenerator::typeInitStruct(const TypeEntryCPtr &te)
 {
-    return cppApiVariableName(te->targetLangPackage()) + u'['
-        + getTypeIndexVariableName(te) + u']';
+    return typeInitStructHelper(te, getTypeIndexVariableName(te));
 }
 
 void CppGenerator::writeExtendedConverterInitialization(TextStream &s,
index f369ab01bdaac7438318f363f6943993ddaccee3..1b66702c074eabf3e36cda7be1359ec533b64d47 100644 (file)
@@ -148,6 +148,7 @@ private:
                             const AbstractMetaFunctionCList &overloads,
                             const GeneratorContext &classContext) const;
     static void writeArgumentsInitializer(TextStream &s, const OverloadData &overloadData,
+                                          const GeneratorContext &classContext,
                                           ErrorReturn errorReturn = ErrorReturn::Default);
     static void writeCppSelfConversion(TextStream &s,
                                        const GeneratorContext &context,
@@ -176,6 +177,7 @@ private:
                                   ErrorReturn errorReturn);
 
     static QString returnErrorWrongArguments(const OverloadData &overloadData,
+                                             const GeneratorContext &context,
                                              ErrorReturn errorReturn);
 
     static void writeFunctionReturnErrorCheckSection(TextStream &s,
@@ -285,6 +287,7 @@ private:
      *   \param overloadData the overload data describing all the possible overloads for the function/method
      */
     void writeOverloadedFunctionDecisor(TextStream &s, const OverloadData &overloadData,
+                                        const GeneratorContext &classContext,
                                         ErrorReturn errorReturn) const;
     /// Recursive auxiliar method to the other writeOverloadedFunctionDecisor.
     void writeOverloadedFunctionDecisorEngine(TextStream &s,
@@ -371,6 +374,7 @@ private:
                                              const AbstractMetaFunctionCPtr &func,
                                              bool usePyArgs,
                                              const OverloadData &overloadData,
+                                             const GeneratorContext &classContext,
                                              ErrorReturn errorReturn);
 
     /// Returns a string containing the name of an argument for the given function and argument index.
@@ -484,6 +488,7 @@ private:
     void writeSmartPointerConverterInitialization(TextStream &s, const AbstractMetaType &ype) const;
 
     static QString typeInitStruct(const TypeEntryCPtr &te);
+    static QString typeInitStruct(const GeneratorContext &context);
     static void writeExtendedConverterInitialization(TextStream &s,
                                                      const TypeEntryCPtr &externalType,
                                                      const AbstractMetaClassCList &conversions);
@@ -554,6 +559,8 @@ private:
     void clearTpFuncs();
     static QString chopType(QString s);
 
+    static QString typeInitStructHelper(const TypeEntryCPtr &te, const QString &varName);
+
     QHash<QString, QString> m_tpFuncs;
     QHash<QString, QString> m_nbFuncs;
 };
index a1417e5d9e360cf4236ca588152db87dc985205d..b25a3df428c8e10c8d242a7a476789e8542080ba 100644 (file)
@@ -688,27 +688,37 @@ QString ShibokenGenerator::converterObject(const AbstractMetaType &type)
     return converterObject(typeEntry);
 }
 
+static QString sbkEnumPrivate(const QString &name)
+{
+    return "PepType_SETP(reinterpret_cast<SbkEnumType *>("_L1 + name  + "))"_L1;
+}
+
 QString ShibokenGenerator::converterObject(const TypeEntryCPtr &type)
 {
-    if (isExtendedCppPrimitive(type))
-        return QString::fromLatin1("Shiboken::Conversions::PrimitiveTypeConverter<%1>()")
-                                  .arg(type->qualifiedCppName());
-    if (type->isWrapperType())
-        return QString::fromLatin1("PepType_SOTP(reinterpret_cast<PyTypeObject *>(%1))->converter")
-                                  .arg(cpythonTypeNameExt(type));
+    if (isExtendedCppPrimitive(type)) {
+        return "Shiboken::Conversions::PrimitiveTypeConverter<"_L1
+               + type->qualifiedCppName() + ">()"_L1;
+    }
+
+    if (type->isWrapperType()) {
+        return "PepType_SOTP(reinterpret_cast<PyTypeObject *>("_L1
+               + cpythonTypeNameExt(type) + "))->converter"_L1;
+    }
+
     if (type->isEnum() || type->isFlags())
-        return QString::fromLatin1("PepType_SETP(reinterpret_cast<SbkEnumType *>(%1))->converter")
-                                  .arg(cpythonTypeNameExt(type));
+        return sbkEnumPrivate(cpythonTypeNameExt(type)) + "->converter"_L1;
 
     if (type->isArray()) {
-        qDebug() << "Warning: no idea how to handle the Qt5 type " << type->qualifiedCppName();
+        qCWarning(lcShiboken, "Warning: no idea how to handle the Qt type \"%s\"",
+                  qPrintable(type->qualifiedCppName()));
         return {};
     }
 
     /* the typedef'd primitive types case */
     auto pte = std::dynamic_pointer_cast<const PrimitiveTypeEntry>(type);
     if (!pte) {
-        qDebug() << "Warning: the Qt5 primitive type is unknown" << type->qualifiedCppName();
+        qCWarning(lcShiboken, "Warning: the Qt primitive type \"%s\" is unknown",
+                  qPrintable(type->qualifiedCppName()));
         return {};
     }
     pte = basicReferencedTypeEntry(pte);
index ffa73394ff722e080a7c19140d350a8e3d8c2039..9b67683ebad6119588d84be01c708010ad198f82 100644 (file)
@@ -1685,7 +1685,7 @@ static inline bool isNone(const PyObject *o)
     return o == nullptr || o == Py_None;
 }
 
-static void removeRefCountKey(SbkObject *self, const char *key)
+static void removeRefCountKey(SbkObject *self, const std::string &key)
 {
     if (self->d->referredObjects) {
         const auto iterPair = self->d->referredObjects->equal_range(key);
@@ -1696,8 +1696,10 @@ static void removeRefCountKey(SbkObject *self, const char *key)
     }
 }
 
-void keepReference(SbkObject *self, const char *key, PyObject *referredObject, bool append)
+void keepReference(SbkObject *self, const char *keyC, PyObject *referredObject, bool append)
 {
+    std::string key(keyC);
+
     if (isNone(referredObject)) {
         removeRefCountKey(self, key);
         return;
@@ -1729,7 +1731,7 @@ void keepReference(SbkObject *self, const char *key, PyObject *referredObject, b
 void removeReference(SbkObject *self, const char *key, PyObject *referredObject)
 {
     if (!isNone(referredObject))
-        removeRefCountKey(self, key);
+        removeRefCountKey(self, std::string(key));
 }
 
 void clearReferences(SbkObject *self)
index f31b8f4f7f430dfecbee26fdadd18c8207a8958b..fe6f5795f8e441cb58bfaf63b96dbf79563b6c4d 100644 (file)
@@ -120,6 +120,8 @@ static bool currentOpcode_Is_CallMethNoArgs()
     // We look into the currently active operation if we are going to call
     // a method with zero arguments.
     auto *frame = PyEval_GetFrame();
+    if (frame == nullptr) // PYSIDE-2796, has been observed to fail
+        return false;
 #if !Py_LIMITED_API && !defined(PYPY_VERSION)
     auto *f_code = PyFrame_GetCode(frame);
 #else
index 1471cd7fe141c1f2ce21a9ccf4f0fb881f034477..b5e87ca5a7b77f934da01a932466a5e4323df63a 100644 (file)
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "sbkstring.h"
+#include "sbkenum.h"
 #include "sbkstaticstrings_p.h"
 #include "autodecref.h"
 
@@ -14,6 +15,11 @@ bool checkIterable(PyObject *obj)
     return PyObject_HasAttr(obj, Shiboken::PyMagicName::iter());
 }
 
+bool checkIterableArgument(PyObject *obj)
+{
+    return checkIterable(obj) && !Shiboken::Enum::check(obj);
+}
+
 static PyObject *initPathLike()
 {
     PyObject *PathLike{};
index f91847c112105114a345e9f49000d38728de06d1..ebc5428c7cdb3a1b8960d83c7d44cfba770c0238 100644 (file)
@@ -13,6 +13,8 @@ namespace String
 {
     LIBSHIBOKEN_API bool check(PyObject *obj);
     LIBSHIBOKEN_API bool checkIterable(PyObject *obj);
+    /// Check for iterable function arguments (excluding enumerations)
+    LIBSHIBOKEN_API bool checkIterableArgument(PyObject *obj);
     LIBSHIBOKEN_API bool checkPath(PyObject *path);
     LIBSHIBOKEN_API bool checkType(PyTypeObject *obj);
     LIBSHIBOKEN_API bool checkChar(PyObject *obj);
index 5650e2bc148d709fa0e59dde3d4ee81c16fc767f..485cc90cbe94a379ae0f70dceac26286582ec5e0 100644 (file)
@@ -15,6 +15,7 @@ import inspect
 import sys
 import types
 from shibokensupport.signature import get_signature as get_sig
+from enum import Enum
 
 
 """
@@ -158,6 +159,14 @@ class ExactEnumerator(object):
                 self.collision_track.add(thing_name)
 
         init_signature = getattr(klass, "__signature__", None)
+        # PYSIDE-2752: Enums without values will not have a constructor, so
+        # we set the init_signature to None, to avoid having an empty pyi
+        # entry, like:
+        #    class QCborTag(enum.IntEnum):
+        #  or
+        #    class BeginFrameFlag(enum.Flag):
+        if isinstance(klass, type(Enum)):
+            init_signature = None
         # sort by class then enum value
         enums.sort(key=lambda tup: (tup[1], tup[2].value))
 
index ce12dd6c8d1331f6905c0ec0e652b2beb449bcd0..466896c2b20e0ca206b991424ca934fd9d878698 100644 (file)
@@ -92,6 +92,11 @@ class Formatter(Writer):
         optional_searcher = re.compile(pattern, flags=re.VERBOSE)
 
         def optional_replacer(source):
+            # PYSIDE-2517: findChild/findChildren type hints:
+            # PlaceHolderType fix to avoid the '~' from TypeVar.__repr__
+            if "~PlaceHolderType" in str(source):
+                source = str(source).replace("~PlaceHolderType", "PlaceHolderType")
+
             return optional_searcher.sub(replace, str(source))
         self.optional_replacer = optional_replacer
         # self.level is maintained by enum_sig.py
@@ -279,15 +284,23 @@ def generate_pyi(import_name, outpath, options):
                         wr.print("import " + imp)
                 wr.print()
                 for mod, imports in filter_from_imports(FROM_IMPORTS, text):
-                    import_args = ', '.join(imports)
+                    # Sorting, and getting uniques to avoid duplications
+                    # on "Iterable" having a couple of entries.
+                    import_args = ', '.join(sorted(set(imports)))
                     if mod is None:
                         # special case, a normal import
                         wr.print(f"import {import_args}")
                     else:
                         wr.print(f"from {mod} import {import_args}")
+                # Adding extra typing import for types that are used in
+                # the followed generated lines
+                wr.print("from typing import TypeAlias, TypeVar")
                 wr.print()
                 wr.print()
                 wr.print("NoneType: TypeAlias = type[None]")
+                # We use it only in QtCore at the moment, but this
+                # could be extended to other modules.
+                wr.print("PlaceHolderType = TypeVar(\"PlaceHolderType\", bound=QObject)")
                 wr.print()
             else:
                 wr.print(line)
index 944a928e688ad5b8316b6f2e610dbd492fc8e6b1..5b3aba1943f7932cb8c248d1b532e0e52ff674ca 100644 (file)
@@ -37,6 +37,11 @@ QImageCleanupFunction = typing.Callable
 # Until we can force it to create Optional[t] again, we use this.
 NoneType = type(None)
 
+# PYSIDE-2517: findChild/findChildren type hints:
+# Placeholder so it does not trigger an UNDEFINED error while building.
+# Later it will be bound to a QObject, within the QtCore types extensions
+PlaceHolderType = TypeVar("PlaceHolderType")
+
 _S = TypeVar("_S")
 
 MultiMap = typing.DefaultDict[str, typing.List[str]]
@@ -206,6 +211,7 @@ type_map.update({
     "int": int,
     "List": ArrayLikeVariable,
     "Optional": typing.Optional,
+    "Iterable": typing.Iterable,
     "long": int,
     "long long": int,
     "nullptr": None,
@@ -462,7 +468,7 @@ def init_smart():
 
 # The PySide Part
 def init_PySide6_QtCore():
-    from PySide6.QtCore import Qt, QUrl, QDir, QKeyCombination
+    from PySide6.QtCore import Qt, QUrl, QDir, QKeyCombination, QObject
     from PySide6.QtCore import QRect, QRectF, QSize, QPoint, QLocale, QByteArray
     from PySide6.QtCore import QMarginsF  # 5.9
     from PySide6.QtCore import SignalInstance
@@ -471,6 +477,7 @@ def init_PySide6_QtCore():
         from PySide6.QtCore import Connection
     except ImportError:
         pass
+
     type_map.update({
         "' '": " ",
         "'%'": "%",
@@ -485,6 +492,8 @@ def init_PySide6_QtCore():
         "size_t": int,
         "NULL": None,  # 5.6, MSVC
         "nullptr": None,  # 5.9
+        # PYSIDE-2517: findChild/findChildren type hints:
+        "PlaceHolderType": typing.TypeVar("PlaceHolderType", bound=QObject),
         "PyBuffer": typing.Union[bytes, bytearray, memoryview],
         "PyByteArray": bytearray,
         "PyBytes": typing.Union[bytes, bytearray, memoryview],
index 9b48ab44291f2f489c5d2a1ad0404271826c3f9b..4c7aebf4a1c1f4db5cd792583c1f5ac3a5dd924d 100644 (file)
@@ -296,6 +296,11 @@ def to_string(thing):
     # i.e. it must be an idempotent mapping.
     if isinstance(thing, str):
         return thing
+    # PYSIDE-2517: findChild/findChildren type hints:
+    # TypeVar doesn't have a __qualname__ attribute,
+    # so we fall back to use __name__ before the next condition.
+    if isinstance(thing, typing.TypeVar):
+        return get_name(thing)
     if hasattr(thing, "__name__") and thing.__module__ != "typing":
         m = thing.__module__
         dot = "." in str(thing) or m not in (thing.__qualname__, "builtins")
index 6c24f417fa686c1ea7eab091300681eea447d3bc..ea19a11f5a88df86bad52b4ba69a931faa8ed472 100644 (file)
@@ -232,7 +232,7 @@ def git_command(versions: List[str], pattern: str):
         task_number_match = task_number_re.match(task)
         if task_number_match:
             task_number = int(task_number_match.group(1))
-        entry = {"title": title, "task": task, "task-number": task_number}
+        entry = {"title": title, "task": task, "task-number": str(task_number)}
         if "shiboken" in title:
             if sha not in shiboken6_commits:
                 shiboken6_commits[sha] = entry
@@ -310,7 +310,7 @@ def gen_list(d: Dict[str, Dict[str, str]]) -> str:
 
 
 def sort_dict(d: Dict[str, Dict[str, str]]) -> Dict[str, Dict[str, str]]:
-    return dict(sorted(d.items(), key=lambda kv: kv[1]['task-number']))
+    return dict(sorted(d.items(), key=lambda kv: int(kv[1]['task-number'])))
 
 
 def sort_changelog(c: List[Tuple[int, str]]) -> List[Tuple[int, str]]:
index 039fa9431cf3a0ad6ebfe983c0e41b3d9f907a02..1502b5d703451be09a53b52eec8312e0aa5d9adc 100644 (file)
@@ -3,6 +3,7 @@
 
 import logging
 import shutil
+import re
 import os
 import stat
 import sys
@@ -17,6 +18,7 @@ from tqdm import tqdm
 # the tag number does not matter much since we update the sdk later
 DEFAULT_SDK_TAG = 6514223
 ANDROID_NDK_VERSION = "26b"
+ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125"
 
 
 def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False,
@@ -83,9 +85,9 @@ class SdkManager:
                     accept_prompts=accept_license, show_stdout=show_stdout)
 
 
-def _unpack(zip_file: Path, destination: Path):
+def extract_zip(file: Path, destination: Path):
     """
-    Unpacks the zip_file into destination preserving all permissions
+    Unpacks the zip file into destination preserving all permissions
 
     TODO: Try to use zipfile module. Currently we cannot use zipfile module here because
     extractAll() does not preserve permissions.
@@ -97,10 +99,29 @@ def _unpack(zip_file: Path, destination: Path):
         raise RuntimeError("Unable to find program unzip. Use `sudo apt-get install unzip`"
                            "to install it")
 
-    command = [unzip, zip_file, "-d", destination]
+    command = [unzip, str(file), "-d", str(destination)]
     run_command(command=command, show_stdout=True)
 
 
+def extract_dmg(file: Path, destination: Path):
+    output = run_command(['hdiutil', 'attach', '-nobrowse', '-readonly', str(file)],
+                         show_stdout=True, capture_stdout=True)
+
+    # find the mounted volume
+    result = re.search(r'/Volumes/(.*)', output)
+    if not result:
+        raise RuntimeError(f"Unable to find mounted volume for file {file}")
+    mounted_vol_name = result.group(1)
+    if not mounted_vol_name:
+        raise RuntimeError(f"Unable to find mounted volume for file {file}")
+
+    # copy files
+    shutil.copytree(f'/Volumes/{mounted_vol_name}/', destination, dirs_exist_ok=True)
+
+    # Detach mounted volume
+    run_command(['hdiutil', 'detach', f'/Volumes/{mounted_vol_name}'])
+
+
 def _download(url: str, destination: Path):
     """
     Download url to destination
@@ -110,7 +131,6 @@ def _download(url: str, destination: Path):
     with DownloadProgressBar(unit='B', unit_scale=True, miniters=1, desc=url.split('/')[-1]) as t:
         download_path, headers = request.urlretrieve(url=url, filename=destination,
                                                      reporthook=t.update_to)
-    assert headers["Content-Type"] == "application/zip"
     assert Path(download_path).resolve() == destination
 
 
@@ -119,22 +139,40 @@ def download_android_ndk(ndk_path: Path):
     Downloads the given ndk_version into ndk_path
     """
     ndk_path = ndk_path / "android-ndk"
-    ndk_zip_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-linux.zip"
-    ndk_version_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}"
+    ndk_extension = "dmg" if sys.platform == "darwin" else "zip"
+    ndk_zip_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"
+    ndk_version_path = ""
+    if sys.platform == "linux":
+        ndk_version_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}"
+    elif sys.platform == "darwin":
+        ndk_version_path = (ndk_path
+                            / f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app/Contents/NDK")
+    else:
+        raise RuntimeError(f"Unsupported platform {sys.platform}")
 
     if ndk_version_path.exists():
         print(f"NDK path found in {str(ndk_version_path)}")
     else:
         ndk_path.mkdir(parents=True, exist_ok=True)
         url = (f"https://dl.google.com/android/repository"
-               f"/android-ndk-r{ANDROID_NDK_VERSION}-linux.zip")
+               f"/android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}")
 
         print(f"Downloading Android Ndk version r{ANDROID_NDK_VERSION}")
         _download(url=url, destination=ndk_zip_path)
 
         print("Unpacking Android Ndk")
-        _unpack(zip_file=(ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-linux.zip"),
-                destination=ndk_path)
+        if sys.platform == "darwin":
+            extract_dmg(file=(ndk_path
+                              / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"
+                              ),
+                        destination=ndk_path)
+            ndk_version_path = (ndk_version_path
+                                / f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app/Contents/NDK")
+        else:
+            extract_zip(file=(ndk_path
+                              / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"
+                              ),
+                        destination=ndk_path)
 
     return ndk_version_path
 
@@ -143,10 +181,12 @@ def download_android_commandlinetools(android_sdk_dir: Path):
     """
     Downloads Android commandline tools into cltools_path.
     """
+    sdk_platform = sys.platform if sys.platform != "darwin" else "mac"
     android_sdk_dir = android_sdk_dir / "android-sdk"
     url = ("https://dl.google.com/android/repository/"
-           f"commandlinetools-linux-{DEFAULT_SDK_TAG}_latest.zip")
-    cltools_zip_path = android_sdk_dir / f"commandlinetools-linux-{DEFAULT_SDK_TAG}_latest.zip"
+           f"commandlinetools-{sdk_platform}-{DEFAULT_SDK_TAG}_latest.zip")
+    cltools_zip_path = (android_sdk_dir
+                        / f"commandlinetools-{sdk_platform}-{DEFAULT_SDK_TAG}_latest.zip")
     cltools_path = android_sdk_dir / "tools"
 
     if cltools_path.exists():
@@ -155,11 +195,11 @@ def download_android_commandlinetools(android_sdk_dir: Path):
         android_sdk_dir.mkdir(parents=True, exist_ok=True)
 
         print("Download Android Command Line Tools: "
-              f"commandlinetools-linux-{DEFAULT_SDK_TAG}_latest.zip")
+              f"commandlinetools-{sys.platform}-{DEFAULT_SDK_TAG}_latest.zip")
         _download(url=url, destination=cltools_zip_path)
 
         print("Unpacking Android Command Line Tools")
-        _unpack(zip_file=cltools_zip_path, destination=android_sdk_dir)
+        extract_zip(file=cltools_zip_path, destination=android_sdk_dir)
 
     return android_sdk_dir
 
@@ -204,6 +244,10 @@ def find_latest_buildtools_version(sdk_manager: SdkManager):
     if not available_build_tools_v:
         raise RuntimeError('Unable to find any build tools available for download')
 
+    # find the latest build tools version that is not a release candidate
+    # release candidates end has rc in the version number
+    available_build_tools_v = [v for v in available_build_tools_v if "rc" not in str(v)]
+
     return max(available_build_tools_v)
 
 
index bda438ccade38beb47b47738ce283ea6b2923cac..200f494cff11b930dba8c725eaab986c6bfe337e 100644 (file)
@@ -18,6 +18,7 @@ from android_utilities import (run_command, download_android_commandlinetools,
                                download_android_ndk, install_android_packages)
 
 # Note: Does not work with PyEnv. Your Host Python should contain openssl.
+# also update the version in ShibokenHelpers.cmake if Python version changes.
 PYTHON_VERSION = "3.11"
 
 SKIP_UPDATE_HELP = ("skip the updation of SDK packages build-tools, platform-tools to"
@@ -86,7 +87,8 @@ if __name__ == "__main__":
 
     parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const",
                         dest="loglevel", const=logging.INFO)
-    parser.add_argument("--api-level", type=str, default="33", help="Android API level to use")
+    parser.add_argument("--api-level", type=str, default="26",
+                        help="Minimum Android API level to use")
     parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred r25c)")
     # sdk path is needed to compile all the Qt Java Acitivity files into Qt6AndroidBindings.jar
     parser.add_argument("--sdk-path", type=str, help="Path to Android SDK")
@@ -184,8 +186,8 @@ if __name__ == "__main__":
             platform_data = PlatformData("x86_64", api_level, "x86_64", "x86_64", "x86-64", "64")
 
         # python path is valid, if Python for android installation exists in python_path
-        python_path = (pyside6_deploy_cache / f"Python-{platform_data.plat_name}-linux-android"
-                       / "_install")
+        python_path = (pyside6_deploy_cache
+                       / f"Python-{platform_data.plat_name}-linux-android" / "_install")
         valid_python_path = python_path.exists()
         if Path(python_path).exists():
             expected_dirs = ["lib", "include"]
@@ -214,6 +216,10 @@ if __name__ == "__main__":
                 )
 
             if not python_ccompile_script.exists():
+                host_system_config_name = run_command("./config.guess", cwd=cpython_dir,
+                                                      dry_run=dry_run, show_stdout=True,
+                                                      capture_stdout=True).strip()
+
                 # use jinja2 to create cross_compile.sh script
                 template = environment.get_template("cross_compile.tmpl.sh")
                 content = template.render(
@@ -221,7 +227,10 @@ if __name__ == "__main__":
                     ndk_path=ndk_path,
                     api_level=platform_data.api_level,
                     android_py_install_path_prefix=pyside6_deploy_cache,
-                    host_python_path=sys.executable
+                    host_python_path=sys.executable,
+                    python_version=PYTHON_VERSION,
+                    host_system_name=host_system_config_name,
+                    host_platform_name=sys.platform
                 )
 
                 logging.info(f"Writing Python cross compile script into {python_ccompile_script}")
@@ -240,13 +249,6 @@ if __name__ == "__main__":
             run_command([f"./{python_ccompile_script.name}"], cwd=cpython_dir, dry_run=dry_run,
                         show_stdout=True)
 
-            # run patchelf to change the SONAME of libpython from libpython3.x.so.1.0 to
-            # libpython3.x.so, to match with python_for_android's Python library. Otherwise,
-            # the Qfp binaries won't be able to link to Python
-            run_command(["patchelf", "--set-soname", f"libpython{PYTHON_VERSION}.so",
-                        f"libpython{PYTHON_VERSION}.so.1.0"], cwd=Path(python_path) / "lib",
-                        dry_run=dry_run)
-
             logging.info(
                 f"Cross compile Python for Android platform {platform_data.plat_name}. "
                 f"Final installation in {python_path}"
@@ -286,15 +288,22 @@ if __name__ == "__main__":
             # give run permission to cross compile script
             qfp_toolchain.chmod(qfp_toolchain.stat().st_mode | stat.S_IEXEC)
 
+        if sys.platform == "linux":
+            host_qt_install_suffix = "gcc_64"
+        elif sys.platform == "darwin":
+            host_qt_install_suffix = "macos"
+        else:
+            raise RuntimeError("Qt for Python cross compilation not supported on this platform")
+
         # run the cross compile script
         logging.info(f"Running Qt for Python cross-compile for platform {platform_data.plat_name}")
         qfp_ccompile_cmd = [sys.executable, "setup.py", "bdist_wheel", "--parallel=9",
                             "--standalone",
                             f"--cmake-toolchain-file={str(qfp_toolchain.resolve())}",
-                            f"--qt-host-path={qt_install_path}/gcc_64",
+                            f"--qt-host-path={qt_install_path}/{host_qt_install_suffix}",
                             f"--plat-name=android_{platform_data.plat_name}",
                             f"--python-target-path={python_path}",
                             (f"--qt-target-path={qt_install_path}/"
-                                f"android_{platform_data.qt_plat_name}"),
+                             f"android_{platform_data.qt_plat_name}"),
                             "--no-qt-tools"]
         run_command(qfp_ccompile_cmd, cwd=pyside_setup_dir, dry_run=dry_run, show_stdout=True)
index a68907591a4360eb8ded87202b976c994ee0fc45..784e822ca83a5c4988452069f1b49e63e8bc6799 100644 (file)
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 set -x -e
 export HOST_ARCH={{ plat_name }}-linux-android
-export TOOLCHAIN={{ ndk_path }}/toolchains/llvm/prebuilt/linux-x86_64/bin
+export TOOLCHAIN={{ ndk_path }}/toolchains/llvm/prebuilt/{{ host_platform_name }}-x86_64/bin
 export TOOL_PREFIX=$TOOLCHAIN/$HOST_ARCH
 export PLATFORM_API={{ api_level }}
 {% if plat_name == "armv7a" -%}
@@ -20,10 +20,10 @@ export RANLIB=$TOOLCHAIN/llvm-ranlib
 export LD=$TOOLCHAIN/ld
 export READELF=$TOOLCHAIN/llvm-readelf
 export CFLAGS='-fPIC -DANDROID'
-./configure --host=$HOST_ARCH --target=$HOST_ARCH --build=x86_64-pc-linux-gnu \
+./configure --host=$HOST_ARCH --target=$HOST_ARCH --build={{ host_system_name }} \
 --with-build-python={{ host_python_path }}  --enable-shared \
 --enable-ipv6 ac_cv_file__dev_ptmx=yes ac_cv_file__dev_ptc=no --without-ensurepip \
 ac_cv_little_endian_double=yes
-make BLDSHARED="$CC -shared" CROSS-COMPILE=$TOOL_PREFIX- CROSS_COMPILE_TARGET=yes
-make install BLDSHARED="$CC -shared" CROSS-COMPILE=$TOOL_PREFIX- \
-CROSS_COMPILE_TARGET=yes prefix={{ android_py_install_path_prefix }}/Python-$HOST_ARCH/_install
+make BLDSHARED="$CC -shared" CROSS-COMPILE=$TOOL_PREFIX- CROSS_COMPILE_TARGET=yes \
+INSTSONAME=libpython{{ python_version }}.so
+make install prefix={{ android_py_install_path_prefix }}/Python-$HOST_ARCH/_install
index b5aa632c046c88873aa392025d29d5468a28f09f..6763d17eb664c9a4bf0a23fcf51be7112c98fead 100644 (file)
@@ -259,14 +259,14 @@ def remove_licenses(s):
     return "\n".join(new_s)
 
 
-def make_zip_archive(zip_name, src, skip_dirs=None):
+def make_zip_archive(zip_file, src, skip_dirs=None):
     src_path = Path(src).expanduser().resolve(strict=True)
     if skip_dirs is None:
         skip_dirs = []
     if not isinstance(skip_dirs, list):
         print("Error: A list needs to be passed for 'skip_dirs'")
         return
-    with zipfile.ZipFile(src_path.parents[0] / Path(zip_name), 'w', zipfile.ZIP_DEFLATED) as zf:
+    with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zf:
         for file in src_path.rglob('*'):
             skip = False
             _parts = file.relative_to(src_path).parts
@@ -291,11 +291,10 @@ def get_code_tabs(files, project_dir, file_format):
     content = "\n"
 
     # Prepare ZIP file, and copy to final destination
-    zip_name = f"{project_dir.name}.zip"
-    make_zip_archive(zip_name, project_dir, skip_dirs=["doc"])
-    zip_src = f"{project_dir}.zip"
-    zip_dst = EXAMPLES_DOC / zip_name
-    shutil.move(zip_src, zip_dst)
+    # Handle examples which only have a dummy pyproject file in the "doc" dir
+    zip_root = project_dir.parent if project_dir.name == "doc" else project_dir
+    zip_name = f"{zip_root.name}.zip"
+    make_zip_archive(EXAMPLES_DOC / zip_name, zip_root, skip_dirs=["doc"])
 
     if file_format == Format.RST:
         content += f":download:`Download this example <{zip_name}>`\n\n"
diff --git a/tools/install-p311.sh b/tools/install-p311.sh
new file mode 100755 (executable)
index 0000000..d56fd34
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/bash
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+if [ ! -d "/Users/qt/python311/bin" ]; then
+    cd /Users/qt/work
+    curl -O https://www.python.org/ftp/python/3.11.9/Python-3.11.9.tar.xz
+    if [ $? -ne 0 ]; then
+        echo "Failed to download Python source code."
+        exit 1
+    fi
+
+    tar xJf Python-3.11.9.tar.xz
+    if [ $? -ne 0 ]; then
+        echo "Failed to extract Python source code."
+        exit 1
+    fi
+
+    cd Python-3.11.9/
+    ./configure --prefix=/Users/qt/python311 --with-openssl=/usr/local/opt/openssl --enable-optimizations
+    if [ $? -ne 0 ]; then
+        echo "Failed to configure Python."
+        exit 1
+    fi
+    make
+    if [ $? -ne 0 ]; then
+        echo "Failed to compile Python."
+        exit 1
+    fi
+    make install
+    if [ $? -ne 0 ]; then
+        echo "Failed to install Python."
+        exit 1
+    fi
+fi
index 01ea06c5e66dfc750167b685dd426bb58e4622b3..3e15c3d98f43a5b42303628111a67d7baea85ab5 100644 (file)
@@ -201,7 +201,7 @@ def overriden_snippet_lines(lines: List[str], start_id: str) -> List[str]:
     return result
 
 
-def get_snippet_override(start_id: str, rel_path: str) -> List[str]:
+def get_snippet_override(start_id: str, rel_path: Path) -> List[str]:
     """Check if the snippet is overridden by a local file under
        sources/pyside6/doc/snippets."""
     file_start_id = start_id.replace(' ', '_')
@@ -220,7 +220,7 @@ def _get_snippets(lines: List[str],
        indicated by pattern ("//! [1]") and return them as a dict by <id>."""
     snippets: Dict[str, List[str]] = {}
     snippet: List[str]
-    done_snippets : List[str] = []
+    done_snippets: List[str] = []
 
     i = 0
     while i < len(lines):
@@ -243,14 +243,14 @@ def _get_snippets(lines: List[str],
             # Find the end of the snippet
             j = i
             while j < len(lines):
-                l = lines[j]
+                line = lines[j]
                 j += 1
 
                 # Add the line to the snippet
-                snippet.append(l)
+                snippet.append(line)
 
                 # Check if the snippet is complete
-                if start_id in get_snippet_ids(l, pattern):
+                if start_id in get_snippet_ids(line, pattern):
                     # End of snippet
                     snippet[len(snippet) - 1] = id_line
                     snippets[start_id] = snippet
@@ -259,7 +259,7 @@ def _get_snippets(lines: List[str],
     return snippets
 
 
-def get_python_example_snippet_override(start_id: str, rel_path: str) -> List[str]:
+def get_python_example_snippet_override(start_id: str, rel_path: Path) -> List[str]:
     """Check if the snippet is overridden by a python example snippet."""
     key = (os.fspath(rel_path), start_id)
     value = python_example_snippet_mapping().get(key)
@@ -275,7 +275,7 @@ def get_python_example_snippet_override(start_id: str, rel_path: str) -> List[st
     return overriden_snippet_lines(lines, start_id)
 
 
-def get_snippets(lines: List[str], rel_path: str) -> List[List[str]]:
+def get_snippets(lines: List[str], rel_path: Path) -> List[List[str]]:
     """Extract (potentially overlapping) snippets from a C++ file indicated
        by '//! [1]'."""
     result = _get_snippets(lines, '//', CPP_SNIPPET_PATTERN)
@@ -288,7 +288,7 @@ def get_snippets(lines: List[str], rel_path: str) -> List[List[str]]:
         if snippet:
             result[snippet_id] = snippet
 
-    return result.values()
+    return list(result.values())
 
 
 def get_license_from_file(lines):
index 208536963c7c976aeab83b2b115fc4ca98814c5b..72dc0fc42d3d57b5944c6213ccd329aebe5c7970 100644 (file)
@@ -78,7 +78,7 @@ if __name__ == '__main__':
     failed = 0
     count = len(options.files)
     for i, file in enumerate(options.files):
-        print(f'{i+1}/{count} {file}')
+        print(f'{i + 1}/{count} {file}')
         if not test_file(file, options.uic):
             failed += 1
     if failed != 0: